halfcycle 0.4.14 → 0.4.15
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 +2525 -541
- 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 +2242 -565
- package/dist/cli.js.map +3 -3
- 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 +1276 -386
- 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.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) {
|
|
@@ -18442,39 +18771,61 @@ function preToolUseNoticeText(text) {
|
|
|
18442
18771
|
return JSON.stringify({ systemMessage: text });
|
|
18443
18772
|
}
|
|
18444
18773
|
function nonBlockingReply(event, text) {
|
|
18445
|
-
if (event === "
|
|
18774
|
+
if (event === "SessionStart") {
|
|
18446
18775
|
return JSON.stringify({
|
|
18447
18776
|
systemMessage: text,
|
|
18448
18777
|
hookSpecificOutput: { hookEventName: event, additionalContext: text }
|
|
18449
18778
|
});
|
|
18450
18779
|
}
|
|
18780
|
+
if (event === "PostToolUse") {
|
|
18781
|
+
return JSON.stringify({ hookSpecificOutput: { hookEventName: event, additionalContext: text } });
|
|
18782
|
+
}
|
|
18451
18783
|
return JSON.stringify({ systemMessage: text });
|
|
18452
18784
|
}
|
|
18453
18785
|
function hookReply(event) {
|
|
18454
18786
|
const queued = [];
|
|
18787
|
+
const forAgent = [];
|
|
18455
18788
|
return {
|
|
18456
18789
|
notice(text) {
|
|
18457
18790
|
queued.push(text);
|
|
18458
18791
|
},
|
|
18459
|
-
|
|
18792
|
+
agentContext(text) {
|
|
18793
|
+
forAgent.push(text);
|
|
18794
|
+
},
|
|
18795
|
+
block(reason, systemMessage) {
|
|
18460
18796
|
const carried = queued.splice(0);
|
|
18461
|
-
return emitBlock(carried.length === 0 ? reason : [reason, ...carried].join("\n"));
|
|
18797
|
+
return emitBlock(carried.length === 0 ? reason : [reason, ...carried].join("\n"), systemMessage);
|
|
18462
18798
|
},
|
|
18463
18799
|
flush() {
|
|
18464
|
-
if (queued.length === 0)
|
|
18465
|
-
return;
|
|
18466
18800
|
const text = queued.splice(0).join("\n");
|
|
18801
|
+
const context = event === "PostToolUse" ? forAgent.splice(0).join("\n") : "";
|
|
18467
18802
|
const code = process.exitCode;
|
|
18468
18803
|
if (code !== void 0 && code !== null && Number(code) !== 0) {
|
|
18469
|
-
|
|
18804
|
+
if (text !== "")
|
|
18805
|
+
writeSync(2, `${text}
|
|
18806
|
+
`);
|
|
18807
|
+
return;
|
|
18808
|
+
}
|
|
18809
|
+
if (context !== "") {
|
|
18810
|
+
process.stdout.write(`${postToolUseReply(text, context)}
|
|
18470
18811
|
`);
|
|
18471
18812
|
return;
|
|
18472
18813
|
}
|
|
18814
|
+
if (text === "")
|
|
18815
|
+
return;
|
|
18473
18816
|
process.stdout.write(`${nonBlockingReply(event, text)}
|
|
18474
18817
|
`);
|
|
18475
18818
|
}
|
|
18476
18819
|
};
|
|
18477
18820
|
}
|
|
18821
|
+
function postToolUseReply(notice, context) {
|
|
18822
|
+
const additionalContext = notice === "" ? context : `${notice}
|
|
18823
|
+
${context}`;
|
|
18824
|
+
return JSON.stringify({ hookSpecificOutput: { hookEventName: "PostToolUse", additionalContext } });
|
|
18825
|
+
}
|
|
18826
|
+
function userPromptContextReply(context) {
|
|
18827
|
+
return JSON.stringify({ hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext: context } });
|
|
18828
|
+
}
|
|
18478
18829
|
function infraWarningText() {
|
|
18479
18830
|
return "[Halfcycle] WARNING: Halfcycle's check service could not be reached, so nothing was checked this time. The session continues without checks.";
|
|
18480
18831
|
}
|
|
@@ -18827,7 +19178,8 @@ function spawnGit(args2, cwd, timeoutMs, extra) {
|
|
|
18827
19178
|
encoding: "utf-8",
|
|
18828
19179
|
...timeoutMs === void 0 ? {} : { timeout: timeoutMs },
|
|
18829
19180
|
...extra?.input === void 0 ? {} : { input: extra.input },
|
|
18830
|
-
...extra?.maxBuffer === void 0 ? {} : { maxBuffer: extra.maxBuffer }
|
|
19181
|
+
...extra?.maxBuffer === void 0 ? {} : { maxBuffer: extra.maxBuffer },
|
|
19182
|
+
...extra?.env === void 0 ? {} : { env: { ...process.env, ...extra.env } }
|
|
18831
19183
|
});
|
|
18832
19184
|
}
|
|
18833
19185
|
function git(args2, cwd, timeoutMs, extra) {
|
|
@@ -18903,6 +19255,9 @@ function remoteHeadLine(remote, repoRoot, timeoutMs, globalArgs = []) {
|
|
|
18903
19255
|
function readGitForFacts(args2, cwd, timeoutMs, input) {
|
|
18904
19256
|
return git(args2, cwd, timeoutMs, { maxBuffer: 64 * 1024 * 1024, ...input === void 0 ? {} : { input } });
|
|
18905
19257
|
}
|
|
19258
|
+
function gitForSave(args2, cwd, timeoutMs, extra = {}) {
|
|
19259
|
+
return git(args2, cwd, timeoutMs, { maxBuffer: 64 * 1024 * 1024, ...extra });
|
|
19260
|
+
}
|
|
18906
19261
|
var EMPTY_TREE_SHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
18907
19262
|
function resolveDiffBase(repoRoot, explicit) {
|
|
18908
19263
|
const named = (explicit ?? "").trim();
|
|
@@ -18964,6 +19319,15 @@ function getRefToWorktreeChangedFiles(ref, repoRoot) {
|
|
|
18964
19319
|
const files = result.value.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
18965
19320
|
return { ok: true, value: files };
|
|
18966
19321
|
}
|
|
19322
|
+
function getUntrackedFiles(repoRoot, paths) {
|
|
19323
|
+
if (paths.length === 0)
|
|
19324
|
+
return { ok: true, value: [] };
|
|
19325
|
+
const pathspecs = paths.map((p) => `:(literal)${p}`);
|
|
19326
|
+
const result = git(["ls-files", "--others", "--exclude-standard", "-z", "--", ...pathspecs], repoRoot);
|
|
19327
|
+
if (!result.ok)
|
|
19328
|
+
return result;
|
|
19329
|
+
return { ok: true, value: result.value.split("\0").filter(Boolean) };
|
|
19330
|
+
}
|
|
18967
19331
|
var PROVE_GIT_TIMEOUT_MS = 3e4;
|
|
18968
19332
|
function pathsDifferingFromHead(repoRoot, timeoutMs = PROVE_GIT_TIMEOUT_MS) {
|
|
18969
19333
|
const result = git(["--no-optional-locks", "status", "--porcelain=v1", "-z", "--no-renames", "--untracked-files=all"], repoRoot, timeoutMs);
|
|
@@ -20767,6 +21131,17 @@ var MARKER_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
|
20767
21131
|
var MARKER_PREFIX = "halfcycle-session-";
|
|
20768
21132
|
var MARKER_SUFFIX = ".ref";
|
|
20769
21133
|
var DEDUPE_SUFFIX = ".eval";
|
|
21134
|
+
var OWN_WORK_PREFIX = "halfcycle-own-";
|
|
21135
|
+
var OWN_WORK_SUFFIX = ".jsonl";
|
|
21136
|
+
var FINDINGS_PREFIX = "halfcycle-findings-";
|
|
21137
|
+
var FINDINGS_SUFFIX = ".json";
|
|
21138
|
+
var FINDINGS_ID_SOURCE = "^[A-Za-z0-9-]{1,128}$";
|
|
21139
|
+
var FINDINGS_ID = new RegExp(FINDINGS_ID_SOURCE);
|
|
21140
|
+
function findingsPath(sessionId) {
|
|
21141
|
+
if (typeof sessionId !== "string" || !FINDINGS_ID.test(sessionId))
|
|
21142
|
+
return null;
|
|
21143
|
+
return join6(markerDir(), `${FINDINGS_PREFIX}${sessionId}${FINDINGS_SUFFIX}`);
|
|
21144
|
+
}
|
|
20770
21145
|
function markerDir() {
|
|
20771
21146
|
const raw = process.env["TMPDIR"] || tmpdir();
|
|
20772
21147
|
return raw.replace(/\/+$/, "");
|
|
@@ -20873,6 +21248,7 @@ function digestChangeState(base, entries) {
|
|
|
20873
21248
|
function orphanSweep(repoRoot, refKnown, keepSessionId, now = Date.now(), livenessCommon) {
|
|
20874
21249
|
if (livenessCommon !== void 0)
|
|
20875
21250
|
sweepLiveness(livenessCommon, now);
|
|
21251
|
+
sweepSessionFiles(keepSessionId, now);
|
|
20876
21252
|
const dir = markerDir();
|
|
20877
21253
|
const hash2 = repoHash(repoRoot);
|
|
20878
21254
|
const keepName = keepSessionId ? markerFileName(repoRoot, keepSessionId) : markerFileName(repoRoot);
|
|
@@ -20912,6 +21288,33 @@ function orphanSweep(repoRoot, refKnown, keepSessionId, now = Date.now(), livene
|
|
|
20912
21288
|
}
|
|
20913
21289
|
}
|
|
20914
21290
|
}
|
|
21291
|
+
function sweepSessionFiles(keepSessionId, now) {
|
|
21292
|
+
const dir = markerDir();
|
|
21293
|
+
let names;
|
|
21294
|
+
try {
|
|
21295
|
+
names = readdirSync3(dir);
|
|
21296
|
+
} catch {
|
|
21297
|
+
return;
|
|
21298
|
+
}
|
|
21299
|
+
const kinds = [
|
|
21300
|
+
[FINDINGS_PREFIX, FINDINGS_SUFFIX],
|
|
21301
|
+
[OWN_WORK_PREFIX, OWN_WORK_SUFFIX]
|
|
21302
|
+
];
|
|
21303
|
+
for (const name of names) {
|
|
21304
|
+
for (const [prefix, suffix] of kinds) {
|
|
21305
|
+
if (!name.startsWith(prefix) || !name.endsWith(suffix))
|
|
21306
|
+
continue;
|
|
21307
|
+
if (keepSessionId !== void 0 && name === `${prefix}${keepSessionId}${suffix}`)
|
|
21308
|
+
continue;
|
|
21309
|
+
const full = join6(dir, name);
|
|
21310
|
+
try {
|
|
21311
|
+
if (now - statSync(full).mtimeMs > MARKER_TTL_MS)
|
|
21312
|
+
unlinkSync3(full);
|
|
21313
|
+
} catch {
|
|
21314
|
+
}
|
|
21315
|
+
}
|
|
21316
|
+
}
|
|
21317
|
+
}
|
|
20915
21318
|
function observeThrottlePath(folder) {
|
|
20916
21319
|
return join6(markerDir(), `${MARKER_PREFIX}${repoHash(folder)}.observe-throttle`);
|
|
20917
21320
|
}
|
|
@@ -21788,6 +22191,21 @@ function parseTimeLog(text) {
|
|
|
21788
22191
|
}
|
|
21789
22192
|
return out;
|
|
21790
22193
|
}
|
|
22194
|
+
function readSessionTimeLines(sessionId, deps = {}) {
|
|
22195
|
+
try {
|
|
22196
|
+
const read = readConfig(deps.env ?? process.env);
|
|
22197
|
+
if (read.config === null)
|
|
22198
|
+
return [];
|
|
22199
|
+
const path = timeLogPath(read.config.guardEngagementId, deps.home);
|
|
22200
|
+
if (path === null)
|
|
22201
|
+
return [];
|
|
22202
|
+
const needle = JSON.stringify(sessionId);
|
|
22203
|
+
const kept = readFileSync11(path, "utf-8").split("\n").filter((raw) => raw.includes(needle));
|
|
22204
|
+
return parseTimeLog(kept.join("\n")).filter((line2) => line2.session === sessionId);
|
|
22205
|
+
} catch {
|
|
22206
|
+
return [];
|
|
22207
|
+
}
|
|
22208
|
+
}
|
|
21791
22209
|
function shortHash(text) {
|
|
21792
22210
|
return createHash2("sha256").update(text, "utf8").digest("hex").slice(0, 16);
|
|
21793
22211
|
}
|
|
@@ -21809,8 +22227,8 @@ function separatorOf(path) {
|
|
|
21809
22227
|
function subagentsFolderOf(transcript) {
|
|
21810
22228
|
if (!transcript.endsWith(".jsonl") || transcript.length === ".jsonl".length)
|
|
21811
22229
|
return null;
|
|
21812
|
-
const
|
|
21813
|
-
return `${transcript.slice(0, -".jsonl".length)}${
|
|
22230
|
+
const sep5 = separatorOf(transcript);
|
|
22231
|
+
return `${transcript.slice(0, -".jsonl".length)}${sep5}subagents`;
|
|
21814
22232
|
}
|
|
21815
22233
|
function subagentRecordName(agentId) {
|
|
21816
22234
|
return /^[A-Za-z0-9_-]+$/.test(agentId) ? `agent-${agentId}.jsonl` : null;
|
|
@@ -22350,7 +22768,7 @@ async function pruneTimeLog(path, openedAt) {
|
|
|
22350
22768
|
written = true;
|
|
22351
22769
|
let seen = Buffer.byteLength(text, "utf-8");
|
|
22352
22770
|
for (; ; ) {
|
|
22353
|
-
await new Promise((
|
|
22771
|
+
await new Promise((resolve8) => setTimeout(resolve8, PRUNE_SETTLE_MS));
|
|
22354
22772
|
const after = readFileSync11(aside);
|
|
22355
22773
|
if (after.length <= seen)
|
|
22356
22774
|
break;
|
|
@@ -22468,102 +22886,628 @@ async function sendTimeRecord(deps = {}) {
|
|
|
22468
22886
|
}
|
|
22469
22887
|
}
|
|
22470
22888
|
|
|
22471
|
-
// dist/
|
|
22472
|
-
|
|
22473
|
-
|
|
22474
|
-
|
|
22475
|
-
|
|
22476
|
-
|
|
22889
|
+
// dist/own-work.js
|
|
22890
|
+
import { appendFileSync as appendFileSync3, lstatSync, readFileSync as readFileSync12, realpathSync as realpathSync4 } from "node:fs";
|
|
22891
|
+
import { basename, dirname as dirname5, isAbsolute, join as join10, relative as relative4, resolve as resolve3, sep as sep2 } from "node:path";
|
|
22892
|
+
|
|
22893
|
+
// dist/risky/types.js
|
|
22894
|
+
var RISKY_ACTION_TYPES = [
|
|
22895
|
+
"delete-files",
|
|
22896
|
+
"discard-unsaved",
|
|
22897
|
+
"rewrite-shared-history",
|
|
22898
|
+
"delete-unmerged-work",
|
|
22899
|
+
"wipe-data",
|
|
22900
|
+
"read-credentials",
|
|
22901
|
+
"publish",
|
|
22902
|
+
"deploy",
|
|
22903
|
+
"shrink-record",
|
|
22904
|
+
"outside-own-copy"
|
|
22905
|
+
];
|
|
22906
|
+
function riskyTypeOrder(type) {
|
|
22907
|
+
return RISKY_ACTION_TYPES.indexOf(type);
|
|
22477
22908
|
}
|
|
22478
|
-
|
|
22479
|
-
|
|
22909
|
+
var CHECKED_TOOLS = ["Bash", "Read", "Grep", "Write", "Edit", "MultiEdit", "NotebookEdit", "Agent"];
|
|
22910
|
+
var RECORDED_ONLY_TOOLS = ["PowerShell"];
|
|
22911
|
+
|
|
22912
|
+
// dist/own-work.js
|
|
22913
|
+
var OWN_WORK_SHELL_TOOLS = ["Bash", "PowerShell"];
|
|
22914
|
+
var SHELL = new Set(OWN_WORK_SHELL_TOOLS);
|
|
22915
|
+
var MATCHED = /* @__PURE__ */ new Set([...CHECKED_TOOLS, ...RECORDED_ONLY_TOOLS]);
|
|
22916
|
+
function isOwnWorkShellTool(toolName) {
|
|
22917
|
+
return typeof toolName === "string" && SHELL.has(toolName) && MATCHED.has(toolName);
|
|
22918
|
+
}
|
|
22919
|
+
function ownWorkFileName(sessionId) {
|
|
22920
|
+
return `${OWN_WORK_PREFIX}${sessionId}${OWN_WORK_SUFFIX}`;
|
|
22921
|
+
}
|
|
22922
|
+
function ownWorkPath(sessionId, dir) {
|
|
22923
|
+
if (typeof sessionId !== "string" || !livenessIdOk(sessionId))
|
|
22924
|
+
return null;
|
|
22925
|
+
return join10(dir ?? markerDir(), ownWorkFileName(sessionId));
|
|
22926
|
+
}
|
|
22927
|
+
function inputString2(input, key) {
|
|
22928
|
+
if (typeof input !== "object" || input === null)
|
|
22929
|
+
return null;
|
|
22930
|
+
const value = input[key];
|
|
22931
|
+
return typeof value === "string" && value !== "" ? value : null;
|
|
22932
|
+
}
|
|
22933
|
+
function ownShellLine(input, at) {
|
|
22934
|
+
return { at: at.toISOString(), kind: "shell", agent: inputString2(input, "agent_id"), toolUse: inputString2(input, "tool_use_id") };
|
|
22935
|
+
}
|
|
22936
|
+
function ownEditLine(path, at) {
|
|
22937
|
+
return { at: at.toISOString(), kind: "edit", path };
|
|
22938
|
+
}
|
|
22939
|
+
function append(sessionId, line2, dir) {
|
|
22940
|
+
const path = ownWorkPath(sessionId, dir);
|
|
22941
|
+
if (path === null)
|
|
22942
|
+
return;
|
|
22943
|
+
appendFileSync3(path, `${JSON.stringify(line2)}
|
|
22944
|
+
`, { mode: 384 });
|
|
22945
|
+
}
|
|
22946
|
+
function recordOwnShell(input, at, deps = {}) {
|
|
22480
22947
|
try {
|
|
22481
|
-
|
|
22482
|
-
|
|
22948
|
+
if (!isOwnWorkShellTool(inputString2(input, "tool_name")))
|
|
22949
|
+
return;
|
|
22950
|
+
append(inputString2(input, "session_id"), ownShellLine(input, at), deps.dir);
|
|
22483
22951
|
} catch {
|
|
22484
|
-
recordTimeEvent("edit", {});
|
|
22485
|
-
return;
|
|
22486
22952
|
}
|
|
22487
|
-
|
|
22488
|
-
|
|
22489
|
-
|
|
22490
|
-
|
|
22491
|
-
|
|
22492
|
-
|
|
22493
|
-
|
|
22494
|
-
|
|
22495
|
-
|
|
22496
|
-
|
|
22497
|
-
|
|
22498
|
-
|
|
22953
|
+
}
|
|
22954
|
+
function recordOwnEdit(input, filePath, deps = {}) {
|
|
22955
|
+
try {
|
|
22956
|
+
if (filePath === "")
|
|
22957
|
+
return;
|
|
22958
|
+
const base = inputString2(input, "cwd") ?? process.cwd();
|
|
22959
|
+
const path = isAbsolute(filePath) ? filePath : resolve3(base, filePath);
|
|
22960
|
+
append(inputString2(input, "session_id"), ownEditLine(path, (deps.now ?? (() => /* @__PURE__ */ new Date()))()), deps.dir);
|
|
22961
|
+
} catch {
|
|
22962
|
+
}
|
|
22963
|
+
}
|
|
22964
|
+
function parseOwnWork(text) {
|
|
22965
|
+
const out = [];
|
|
22966
|
+
for (const raw of text.split("\n")) {
|
|
22967
|
+
if (raw === "")
|
|
22968
|
+
continue;
|
|
22969
|
+
try {
|
|
22970
|
+
const o = JSON.parse(raw);
|
|
22971
|
+
if (typeof o["at"] !== "string")
|
|
22972
|
+
continue;
|
|
22973
|
+
const nullable2 = (v) => v === null || typeof v === "string";
|
|
22974
|
+
if (o["kind"] === "edit" && typeof o["path"] === "string")
|
|
22975
|
+
out.push({ at: o["at"], kind: "edit", path: o["path"] });
|
|
22976
|
+
else if (o["kind"] === "shell" && nullable2(o["agent"]) && nullable2(o["toolUse"])) {
|
|
22977
|
+
out.push({ at: o["at"], kind: "shell", agent: o["agent"], toolUse: o["toolUse"] });
|
|
22978
|
+
}
|
|
22979
|
+
} catch {
|
|
22499
22980
|
}
|
|
22500
|
-
return;
|
|
22501
22981
|
}
|
|
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;
|
|
22982
|
+
return out;
|
|
22983
|
+
}
|
|
22984
|
+
function readOwnWork(sessionId, dir) {
|
|
22985
|
+
try {
|
|
22986
|
+
const path = ownWorkPath(sessionId, dir);
|
|
22987
|
+
if (path === null)
|
|
22988
|
+
return [];
|
|
22989
|
+
return parseOwnWork(readFileSync12(path, "utf-8"));
|
|
22990
|
+
} catch {
|
|
22991
|
+
return [];
|
|
22520
22992
|
}
|
|
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;
|
|
22993
|
+
}
|
|
22994
|
+
function shellWindows(own, timeLines, sessionId, passAt) {
|
|
22995
|
+
const out = [];
|
|
22996
|
+
for (const line2 of own) {
|
|
22997
|
+
if (line2.kind !== "shell")
|
|
22998
|
+
continue;
|
|
22999
|
+
const from = Date.parse(line2.at);
|
|
23000
|
+
if (Number.isNaN(from))
|
|
23001
|
+
continue;
|
|
23002
|
+
let to = passAt;
|
|
23003
|
+
for (const t of timeLines) {
|
|
23004
|
+
if (t.session !== sessionId || t.agent !== line2.agent)
|
|
23005
|
+
continue;
|
|
23006
|
+
const isOwnLine = line2.toolUse !== null ? t.toolUse === line2.toolUse : t.at === line2.at;
|
|
23007
|
+
if (isOwnLine)
|
|
23008
|
+
continue;
|
|
23009
|
+
const at = Date.parse(t.at);
|
|
23010
|
+
if (Number.isNaN(at) || at < from)
|
|
23011
|
+
continue;
|
|
23012
|
+
if (at < to)
|
|
23013
|
+
to = at;
|
|
22555
23014
|
}
|
|
22556
|
-
|
|
22557
|
-
|
|
22558
|
-
|
|
23015
|
+
out.push({ from, to: Math.max(from, to) });
|
|
23016
|
+
}
|
|
23017
|
+
return out;
|
|
23018
|
+
}
|
|
23019
|
+
function realPathOf(path) {
|
|
23020
|
+
const missing = [];
|
|
23021
|
+
let at = resolve3(path);
|
|
23022
|
+
for (; ; ) {
|
|
23023
|
+
try {
|
|
23024
|
+
return join10(realpathSync4.native(at), ...missing.reverse());
|
|
23025
|
+
} catch {
|
|
23026
|
+
const parent = dirname5(at);
|
|
23027
|
+
if (parent === at)
|
|
23028
|
+
return resolve3(path);
|
|
23029
|
+
missing.push(basename(at));
|
|
23030
|
+
at = parent;
|
|
22559
23031
|
}
|
|
22560
|
-
reply.notice(infraWarningText());
|
|
22561
|
-
return;
|
|
22562
23032
|
}
|
|
22563
|
-
|
|
22564
|
-
|
|
22565
|
-
|
|
22566
|
-
|
|
23033
|
+
}
|
|
23034
|
+
function modifiedAt(path) {
|
|
23035
|
+
try {
|
|
23036
|
+
return lstatSync(path).mtimeMs;
|
|
23037
|
+
} catch {
|
|
23038
|
+
return null;
|
|
23039
|
+
}
|
|
23040
|
+
}
|
|
23041
|
+
function ownEditedFiles(repoRoot, sessionId, deps = {}) {
|
|
23042
|
+
const root = realPathOf(repoRoot);
|
|
23043
|
+
const out = [];
|
|
23044
|
+
const seen = /* @__PURE__ */ new Set();
|
|
23045
|
+
for (const line2 of readOwnWork(sessionId, deps.dir)) {
|
|
23046
|
+
if (line2.kind !== "edit")
|
|
23047
|
+
continue;
|
|
23048
|
+
const abs = realPathOf(line2.path);
|
|
23049
|
+
const rel = relative4(root, abs);
|
|
23050
|
+
if (rel === "" || rel === ".." || rel.startsWith(`..${sep2}`) || isAbsolute(rel))
|
|
23051
|
+
continue;
|
|
23052
|
+
if (modifiedAt(abs) === null)
|
|
23053
|
+
continue;
|
|
23054
|
+
const posix3 = rel.split(sep2).join("/");
|
|
23055
|
+
if (seen.has(posix3))
|
|
23056
|
+
continue;
|
|
23057
|
+
seen.add(posix3);
|
|
23058
|
+
out.push(posix3);
|
|
23059
|
+
}
|
|
23060
|
+
return out;
|
|
23061
|
+
}
|
|
23062
|
+
function ownChangedFiles(repoRoot, files, sessionId, deps = {}) {
|
|
23063
|
+
if (typeof sessionId !== "string" || ownWorkPath(sessionId, deps.dir) === null)
|
|
23064
|
+
return [...files];
|
|
23065
|
+
const own = readOwnWork(sessionId, deps.dir);
|
|
23066
|
+
if (own.length === 0)
|
|
23067
|
+
return [];
|
|
23068
|
+
const edited = /* @__PURE__ */ new Set();
|
|
23069
|
+
for (const line2 of own)
|
|
23070
|
+
if (line2.kind === "edit")
|
|
23071
|
+
edited.add(realPathOf(line2.path));
|
|
23072
|
+
const hasShell = own.some((line2) => line2.kind === "shell");
|
|
23073
|
+
const passAt = (deps.now ?? (() => /* @__PURE__ */ new Date()))().getTime();
|
|
23074
|
+
const timeLines = hasShell ? readSessionTimeLines(sessionId, { env: deps.env, home: deps.home }) : [];
|
|
23075
|
+
const windows = hasShell ? shellWindows(own, timeLines, sessionId, passAt) : [];
|
|
23076
|
+
const inWindow = (mtimeMs) => {
|
|
23077
|
+
if (mtimeMs === null)
|
|
23078
|
+
return false;
|
|
23079
|
+
const ms = Math.floor(mtimeMs);
|
|
23080
|
+
return windows.some((w) => ms >= w.from && ms <= w.to);
|
|
23081
|
+
};
|
|
23082
|
+
const root = resolve3(repoRoot);
|
|
23083
|
+
return files.filter((rel) => {
|
|
23084
|
+
const abs = join10(root, rel);
|
|
23085
|
+
if (edited.has(realPathOf(abs)))
|
|
23086
|
+
return true;
|
|
23087
|
+
if (windows.length === 0)
|
|
23088
|
+
return false;
|
|
23089
|
+
const mtime = modifiedAt(abs);
|
|
23090
|
+
if (mtime !== null)
|
|
23091
|
+
return inWindow(mtime);
|
|
23092
|
+
return inWindow(modifiedAt(dirname5(abs)));
|
|
23093
|
+
});
|
|
23094
|
+
}
|
|
23095
|
+
|
|
23096
|
+
// dist/hooks/findings.js
|
|
23097
|
+
import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
|
|
23098
|
+
import { closeSync, existsSync as existsSync6, openSync, readFileSync as readFileSync13, renameSync as renameSync4, statSync as statSync3, unlinkSync as unlinkSync5, writeFileSync as writeFileSync6 } from "node:fs";
|
|
23099
|
+
function findingKey(firing) {
|
|
23100
|
+
return createHash3("sha256").update(`${firing.guardId}
|
|
23101
|
+
${firing.path ?? ""}
|
|
23102
|
+
${firing.explanation}`).digest("hex");
|
|
23103
|
+
}
|
|
23104
|
+
function asFinding(raw) {
|
|
23105
|
+
if (typeof raw !== "object" || raw === null)
|
|
23106
|
+
return null;
|
|
23107
|
+
const o = raw;
|
|
23108
|
+
const str = (k) => typeof o[k] === "string";
|
|
23109
|
+
const bool = (k) => typeof o[k] === "boolean";
|
|
23110
|
+
if (!str("key") || !str("folder") || !str("guardId") || !str("patternRef") || !str("explanation") || !str("foundAt"))
|
|
23111
|
+
return null;
|
|
23112
|
+
if (o["severity"] !== "info" && o["severity"] !== "warn")
|
|
23113
|
+
return null;
|
|
23114
|
+
if (!bool("announced") || !bool("delivered") || !bool("escalated"))
|
|
23115
|
+
return null;
|
|
23116
|
+
if (o["path"] !== void 0 && typeof o["path"] !== "string")
|
|
23117
|
+
return null;
|
|
23118
|
+
return {
|
|
23119
|
+
key: o["key"],
|
|
23120
|
+
folder: o["folder"],
|
|
23121
|
+
guardId: o["guardId"],
|
|
23122
|
+
patternRef: o["patternRef"],
|
|
23123
|
+
severity: o["severity"],
|
|
23124
|
+
explanation: o["explanation"],
|
|
23125
|
+
...typeof o["path"] === "string" ? { path: o["path"] } : {},
|
|
23126
|
+
foundAt: o["foundAt"],
|
|
23127
|
+
announced: o["announced"],
|
|
23128
|
+
delivered: o["delivered"],
|
|
23129
|
+
escalated: o["escalated"]
|
|
23130
|
+
};
|
|
23131
|
+
}
|
|
23132
|
+
function emptyFindings(session) {
|
|
23133
|
+
return { version: 1, session, findings: [] };
|
|
23134
|
+
}
|
|
23135
|
+
function readFindings(sessionId) {
|
|
23136
|
+
const path = findingsPath(sessionId);
|
|
23137
|
+
if (path === null)
|
|
23138
|
+
return null;
|
|
23139
|
+
const session = sessionId;
|
|
23140
|
+
try {
|
|
23141
|
+
const parsed = JSON.parse(readFileSync13(path, "utf-8"));
|
|
23142
|
+
if (parsed["version"] !== 1 || !Array.isArray(parsed["findings"]))
|
|
23143
|
+
return emptyFindings(session);
|
|
23144
|
+
const findings = [];
|
|
23145
|
+
for (const raw of parsed["findings"]) {
|
|
23146
|
+
const f = asFinding(raw);
|
|
23147
|
+
if (f !== null)
|
|
23148
|
+
findings.push(f);
|
|
23149
|
+
}
|
|
23150
|
+
return { version: 1, session, findings };
|
|
23151
|
+
} catch {
|
|
23152
|
+
return emptyFindings(session);
|
|
23153
|
+
}
|
|
23154
|
+
}
|
|
23155
|
+
function writeFindings(file2) {
|
|
23156
|
+
const path = findingsPath(file2.session);
|
|
23157
|
+
if (path === null)
|
|
23158
|
+
return;
|
|
23159
|
+
const temp = `${path.slice(0, -".json".length)}.${process.pid}.tmp.json`;
|
|
23160
|
+
try {
|
|
23161
|
+
writeFileSync6(temp, `${JSON.stringify(file2)}
|
|
23162
|
+
`, { mode: 384 });
|
|
23163
|
+
renameSync4(temp, path);
|
|
23164
|
+
} catch {
|
|
23165
|
+
try {
|
|
23166
|
+
unlinkSync5(temp);
|
|
23167
|
+
} catch {
|
|
23168
|
+
}
|
|
23169
|
+
}
|
|
23170
|
+
}
|
|
23171
|
+
var FINDINGS_LOCK_WAIT_MS = 1e3;
|
|
23172
|
+
var FINDINGS_LOCK_STALE_MS = 5e3;
|
|
23173
|
+
function lockPathFor(path) {
|
|
23174
|
+
return `${path.slice(0, -".json".length)}.lock.json`;
|
|
23175
|
+
}
|
|
23176
|
+
function pause(ms) {
|
|
23177
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
23178
|
+
}
|
|
23179
|
+
function takeOverStaleLock(lock) {
|
|
23180
|
+
const aside = `${lock}.${process.pid}.${randomUUID2()}.stale`;
|
|
23181
|
+
try {
|
|
23182
|
+
renameSync4(lock, aside);
|
|
23183
|
+
} catch {
|
|
23184
|
+
return false;
|
|
23185
|
+
}
|
|
23186
|
+
let staleIndeed = false;
|
|
23187
|
+
try {
|
|
23188
|
+
staleIndeed = Date.now() - statSync3(aside).mtimeMs > FINDINGS_LOCK_STALE_MS;
|
|
23189
|
+
} catch {
|
|
23190
|
+
staleIndeed = false;
|
|
23191
|
+
}
|
|
23192
|
+
if (!staleIndeed) {
|
|
23193
|
+
try {
|
|
23194
|
+
renameSync4(aside, lock);
|
|
23195
|
+
} catch {
|
|
23196
|
+
try {
|
|
23197
|
+
unlinkSync5(aside);
|
|
23198
|
+
} catch {
|
|
23199
|
+
}
|
|
23200
|
+
}
|
|
23201
|
+
return false;
|
|
23202
|
+
}
|
|
23203
|
+
try {
|
|
23204
|
+
unlinkSync5(aside);
|
|
23205
|
+
} catch {
|
|
23206
|
+
}
|
|
23207
|
+
return true;
|
|
23208
|
+
}
|
|
23209
|
+
function withFindingsLock(sessionId, body, waitMs = FINDINGS_LOCK_WAIT_MS) {
|
|
23210
|
+
const path = findingsPath(sessionId);
|
|
23211
|
+
if (path === null)
|
|
23212
|
+
return void 0;
|
|
23213
|
+
const lock = lockPathFor(path);
|
|
23214
|
+
const deadline = Date.now() + waitMs;
|
|
23215
|
+
for (; ; ) {
|
|
23216
|
+
try {
|
|
23217
|
+
closeSync(openSync(lock, "wx", 384));
|
|
23218
|
+
break;
|
|
23219
|
+
} catch (err) {
|
|
23220
|
+
if (err.code !== "EEXIST")
|
|
23221
|
+
return void 0;
|
|
23222
|
+
let stale = false;
|
|
23223
|
+
try {
|
|
23224
|
+
stale = Date.now() - statSync3(lock).mtimeMs > FINDINGS_LOCK_STALE_MS;
|
|
23225
|
+
} catch {
|
|
23226
|
+
continue;
|
|
23227
|
+
}
|
|
23228
|
+
if (stale && takeOverStaleLock(lock))
|
|
23229
|
+
continue;
|
|
23230
|
+
if (Date.now() >= deadline)
|
|
23231
|
+
return void 0;
|
|
23232
|
+
pause(10);
|
|
23233
|
+
}
|
|
23234
|
+
}
|
|
23235
|
+
try {
|
|
23236
|
+
return body();
|
|
23237
|
+
} finally {
|
|
23238
|
+
try {
|
|
23239
|
+
unlinkSync5(lock);
|
|
23240
|
+
} catch {
|
|
23241
|
+
}
|
|
23242
|
+
}
|
|
23243
|
+
}
|
|
23244
|
+
function takeUndelivered(file2, folder) {
|
|
23245
|
+
const taken = [];
|
|
23246
|
+
const findings = file2.findings.map((f) => {
|
|
23247
|
+
if (f.delivered || folder !== null && f.folder !== folder)
|
|
23248
|
+
return f;
|
|
23249
|
+
taken.push(f);
|
|
23250
|
+
return { ...f, delivered: true };
|
|
23251
|
+
});
|
|
23252
|
+
return { file: { ...file2, findings }, taken };
|
|
23253
|
+
}
|
|
23254
|
+
function deliverFindings(sessionId, folder) {
|
|
23255
|
+
const path = findingsPath(sessionId);
|
|
23256
|
+
if (path === null || !existsSync6(path))
|
|
23257
|
+
return [];
|
|
23258
|
+
try {
|
|
23259
|
+
const taken = withFindingsLock(sessionId, () => {
|
|
23260
|
+
const before = readFindings(sessionId);
|
|
23261
|
+
if (before === null)
|
|
23262
|
+
return [];
|
|
23263
|
+
const { file: file2, taken: given } = takeUndelivered(before, folder);
|
|
23264
|
+
if (given.length > 0)
|
|
23265
|
+
writeFindings(file2);
|
|
23266
|
+
return given;
|
|
23267
|
+
});
|
|
23268
|
+
return taken ?? [];
|
|
23269
|
+
} catch {
|
|
23270
|
+
return [];
|
|
23271
|
+
}
|
|
23272
|
+
}
|
|
23273
|
+
function turnEndBookkeeping(file2, folder, outcome, topLevel, now) {
|
|
23274
|
+
const recorded = [...file2.findings];
|
|
23275
|
+
const firing = /* @__PURE__ */ new Map();
|
|
23276
|
+
const notRun = new Set(outcome.kind === "evaluated" ? outcome.notRun : []);
|
|
23277
|
+
const observed = (f) => f.folder === folder && !notRun.has(f.patternRef);
|
|
23278
|
+
if (outcome.kind === "evaluated") {
|
|
23279
|
+
for (const g of outcome.fired) {
|
|
23280
|
+
if (g.severity === "block")
|
|
23281
|
+
continue;
|
|
23282
|
+
const key = findingKey(g);
|
|
23283
|
+
if (!firing.has(key))
|
|
23284
|
+
firing.set(key, g);
|
|
23285
|
+
}
|
|
23286
|
+
}
|
|
23287
|
+
const removed = [];
|
|
23288
|
+
const kept = [];
|
|
23289
|
+
for (const f of recorded) {
|
|
23290
|
+
const closes = observed(f) && (outcome.kind === "no-own-changes" || outcome.kind === "evaluated" && !firing.has(f.key));
|
|
23291
|
+
(closes ? removed : kept).push(f);
|
|
23292
|
+
}
|
|
23293
|
+
const known = new Set(kept.filter((f) => f.folder === folder).map((f) => f.key));
|
|
23294
|
+
for (const [key, g] of firing) {
|
|
23295
|
+
if (known.has(key))
|
|
23296
|
+
continue;
|
|
23297
|
+
kept.push({
|
|
23298
|
+
key,
|
|
23299
|
+
folder,
|
|
23300
|
+
guardId: g.guardId,
|
|
23301
|
+
patternRef: g.patternRef,
|
|
23302
|
+
severity: g.severity,
|
|
23303
|
+
explanation: g.explanation,
|
|
23304
|
+
...g.path !== void 0 ? { path: g.path } : {},
|
|
23305
|
+
foundAt: now.toISOString(),
|
|
23306
|
+
announced: false,
|
|
23307
|
+
delivered: false,
|
|
23308
|
+
escalated: false
|
|
23309
|
+
});
|
|
23310
|
+
}
|
|
23311
|
+
if (!topLevel) {
|
|
23312
|
+
return { file: { ...file2, findings: kept }, news: { fixed: 0, allFixed: false, found: 0, stillOpen: [] } };
|
|
23313
|
+
}
|
|
23314
|
+
const fixed = removed.filter((f) => f.announced).length;
|
|
23315
|
+
const allFixed = !kept.some((f) => f.announced);
|
|
23316
|
+
let found2 = 0;
|
|
23317
|
+
const stillOpen = [];
|
|
23318
|
+
const next = kept.map((f) => {
|
|
23319
|
+
if (!f.announced) {
|
|
23320
|
+
found2 += 1;
|
|
23321
|
+
return { ...f, announced: true };
|
|
23322
|
+
}
|
|
23323
|
+
if (f.delivered && !f.escalated && observed(f)) {
|
|
23324
|
+
stillOpen.push(f.explanation);
|
|
23325
|
+
return { ...f, escalated: true };
|
|
23326
|
+
}
|
|
23327
|
+
return f;
|
|
23328
|
+
});
|
|
23329
|
+
return { file: { ...file2, findings: next }, news: { fixed, allFixed, found: found2, stillOpen } };
|
|
23330
|
+
}
|
|
23331
|
+
function blockingExplanations(fired) {
|
|
23332
|
+
const blocks = fired.filter((g) => g.severity === "block");
|
|
23333
|
+
return (blocks.length > 0 ? blocks : fired).map((g) => g.explanation);
|
|
23334
|
+
}
|
|
23335
|
+
|
|
23336
|
+
// dist/hooks/finding-lines.js
|
|
23337
|
+
function bullets(explanations) {
|
|
23338
|
+
return explanations.map((e) => `- ${e}`).join("\n");
|
|
23339
|
+
}
|
|
23340
|
+
function distinctBullets(explanations) {
|
|
23341
|
+
return bullets([...new Set(explanations)]);
|
|
23342
|
+
}
|
|
23343
|
+
function foundLine(count) {
|
|
23344
|
+
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.`;
|
|
23345
|
+
}
|
|
23346
|
+
function fixedLine(count, all) {
|
|
23347
|
+
if (all)
|
|
23348
|
+
return "Halfcycle: everything it found earlier is now fixed.";
|
|
23349
|
+
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.`;
|
|
23350
|
+
}
|
|
23351
|
+
function stillOpenMessage(explanations) {
|
|
23352
|
+
const n = explanations.length;
|
|
23353
|
+
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.`;
|
|
23354
|
+
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.";
|
|
23355
|
+
return `${head}
|
|
23356
|
+
${bullets(explanations)}
|
|
23357
|
+
${tail}`;
|
|
23358
|
+
}
|
|
23359
|
+
function stoppedMessage(explanations) {
|
|
23360
|
+
return [
|
|
23361
|
+
"Halfcycle stopped this step because something in the agent's work has to be fixed first:",
|
|
23362
|
+
distinctBullets(explanations),
|
|
23363
|
+
"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."
|
|
23364
|
+
].join("\n");
|
|
23365
|
+
}
|
|
23366
|
+
function couldNotFixMessage(explanations) {
|
|
23367
|
+
return [
|
|
23368
|
+
"Halfcycle: the agent could not fix what stopped its work:",
|
|
23369
|
+
distinctBullets(explanations),
|
|
23370
|
+
"Ask the agent what is in the way, or tell it how you want this handled."
|
|
23371
|
+
].join("\n");
|
|
23372
|
+
}
|
|
23373
|
+
function blockReason(explanations) {
|
|
23374
|
+
return `Halfcycle stopped this step. Fix each of these before you carry on:
|
|
23375
|
+
${distinctBullets(explanations)}`;
|
|
23376
|
+
}
|
|
23377
|
+
function detailsBlock(findings, projectFolder2) {
|
|
23378
|
+
const lines2 = findings.map((f) => {
|
|
23379
|
+
const where = f.folder === projectFolder2 ? f.path : f.path === void 0 ? f.folder : `${f.folder}/${f.path}`;
|
|
23380
|
+
const at = where === void 0 ? "" : ` in ${where}`;
|
|
23381
|
+
return ` [${f.severity.toUpperCase()}] ${f.patternRef}${at}: ${f.explanation}`;
|
|
23382
|
+
});
|
|
23383
|
+
return [
|
|
23384
|
+
"Halfcycle's checks found these in this session's own changes at the end of an earlier turn. Fix each one as part of this turn. If one is intended, tell the person in one plain sentence. When you mention them to the person, use plain words, not the labels in brackets.",
|
|
23385
|
+
...lines2
|
|
23386
|
+
].join("\n");
|
|
23387
|
+
}
|
|
23388
|
+
function turnEndMessage(news) {
|
|
23389
|
+
const parts = [];
|
|
23390
|
+
if (news.fixed > 0)
|
|
23391
|
+
parts.push(fixedLine(news.fixed, news.allFixed));
|
|
23392
|
+
if (news.found > 0)
|
|
23393
|
+
parts.push(foundLine(news.found));
|
|
23394
|
+
if (news.stillOpen.length > 0)
|
|
23395
|
+
parts.push(stillOpenMessage(news.stillOpen));
|
|
23396
|
+
return parts.length === 0 ? null : parts.join("\n");
|
|
23397
|
+
}
|
|
23398
|
+
|
|
23399
|
+
// dist/hooks/post-tool-use.js
|
|
23400
|
+
var HANDLED_EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
23401
|
+
async function runPostToolUse(readSeat = readSeatConfiguration) {
|
|
23402
|
+
const reply = hookReply("PostToolUse");
|
|
23403
|
+
const at = { session: void 0, folder: null };
|
|
23404
|
+
await evaluateEdit(reply, readSeat, at);
|
|
23405
|
+
handOverAtEdit(reply, at);
|
|
23406
|
+
reply.flush();
|
|
23407
|
+
}
|
|
23408
|
+
function handOverAtEdit(reply, at) {
|
|
23409
|
+
if (at.folder === null)
|
|
23410
|
+
return;
|
|
23411
|
+
const code = process.exitCode;
|
|
23412
|
+
if (code !== void 0 && code !== null && Number(code) !== 0)
|
|
23413
|
+
return;
|
|
23414
|
+
const given = deliverFindings(at.session, at.folder);
|
|
23415
|
+
if (given.length === 0)
|
|
23416
|
+
return;
|
|
23417
|
+
reply.agentContext(detailsBlock(given, realPathOf(projectFolder())));
|
|
23418
|
+
}
|
|
23419
|
+
async function evaluateEdit(reply, readSeat, at) {
|
|
23420
|
+
let input;
|
|
23421
|
+
try {
|
|
23422
|
+
const raw = await readStdin();
|
|
23423
|
+
input = JSON.parse(raw);
|
|
23424
|
+
} catch {
|
|
23425
|
+
recordTimeEvent("edit", {});
|
|
23426
|
+
return;
|
|
23427
|
+
}
|
|
23428
|
+
recordTimeEvent("edit", input);
|
|
23429
|
+
const toolName = input.tool_name ?? "";
|
|
23430
|
+
if (!HANDLED_EDIT_TOOLS.has(toolName))
|
|
23431
|
+
return;
|
|
23432
|
+
const isNotebook = toolName === "NotebookEdit";
|
|
23433
|
+
const absFilePath = isNotebook ? input.tool_input?.notebook_path : input.tool_input?.file_path;
|
|
23434
|
+
if (!absFilePath || typeof absFilePath !== "string")
|
|
23435
|
+
return;
|
|
23436
|
+
recordOwnEdit(input, absFilePath);
|
|
23437
|
+
const repoRootResult = getRepoRoot(process.cwd());
|
|
23438
|
+
if (!repoRootResult.ok) {
|
|
23439
|
+
if (firstNoRepoNoticeThisSession(process.cwd(), input.session_id)) {
|
|
23440
|
+
reply.notice(noRepositoryText(repoRootResult));
|
|
23441
|
+
}
|
|
23442
|
+
return;
|
|
23443
|
+
}
|
|
23444
|
+
const repoRoot = repoRootResult.value;
|
|
23445
|
+
at.session = input.session_id;
|
|
23446
|
+
at.folder = realPathOf(repoRoot);
|
|
23447
|
+
const diffContent = isNotebook ? "" : getSingleFileDiff(absFilePath, repoRoot);
|
|
23448
|
+
const { relative: pathRelative } = await import("node:path");
|
|
23449
|
+
const relPath = pathRelative(repoRoot, absFilePath);
|
|
23450
|
+
if (isInstallerOutput(relPath))
|
|
23451
|
+
return;
|
|
23452
|
+
const context = loadChangeSetContext(repoRoot);
|
|
23453
|
+
const changeSet = buildChangeSet([{ path: relPath, diffContent }], {
|
|
23454
|
+
...context,
|
|
23455
|
+
excerpts: readExcerptConfig()
|
|
23456
|
+
});
|
|
23457
|
+
const configResult = readConfig();
|
|
23458
|
+
if (!configResult.config) {
|
|
23459
|
+
await emitRunRecord({ runType: "hook", outcome: "unconfigured", changeSet });
|
|
23460
|
+
if (!firstNoticeThisSession("credential-missing", repoRoot, input.session_id))
|
|
23461
|
+
return;
|
|
23462
|
+
emitNeverConfigured(UNCONFIGURED_EXIT_TOOL_USE, readNeverConfiguredReason(), pinnedEngagementId(projectFolder()));
|
|
23463
|
+
return;
|
|
23464
|
+
}
|
|
23465
|
+
const { guardServiceUrl, guardServiceToken, guardEngagementId } = configResult.config;
|
|
23466
|
+
const seatConfiguration = gatherSeatConfiguration(repoRoot, readSeat);
|
|
23467
|
+
const evaluation = await evaluateWithRefresh({
|
|
23468
|
+
guardServiceUrl,
|
|
23469
|
+
token: guardServiceToken,
|
|
23470
|
+
engagementId: guardEngagementId,
|
|
23471
|
+
request: buildEvaluationRequest(guardEngagementId, changeSet, context, seatConfiguration),
|
|
23472
|
+
warn: warnOnceThisSession("credential-renewed-for-this-run", repoRoot, input.session_id)
|
|
23473
|
+
});
|
|
23474
|
+
const clientResult = evaluation.result;
|
|
23475
|
+
if (!clientResult.ok) {
|
|
23476
|
+
const failure = classifyFailure(clientResult);
|
|
23477
|
+
await emitRunRecord({
|
|
23478
|
+
engagementId: guardEngagementId,
|
|
23479
|
+
runType: "hook",
|
|
23480
|
+
outcome: outcomeForClientFailure(clientResult.kind),
|
|
23481
|
+
failureReason: failure.reason,
|
|
23482
|
+
changeSet,
|
|
23483
|
+
credential: evaluation.token,
|
|
23484
|
+
credentialRefused: failure.kind === "credential"
|
|
23485
|
+
});
|
|
23486
|
+
if (failure.kind === "credential") {
|
|
23487
|
+
if (!firstNoticeThisSession("credential-rejected", repoRoot, input.session_id))
|
|
23488
|
+
return;
|
|
23489
|
+
emitCredentialRejected(
|
|
23490
|
+
failure.statusCode,
|
|
23491
|
+
failure.message,
|
|
23492
|
+
CREDENTIAL_REJECTED_EXIT_TOOL_USE,
|
|
23493
|
+
// The remedy the renewal attempt earned: the sign-in when the account
|
|
23494
|
+
// credential is the thing that was refused, and otherwise the plane's own
|
|
23495
|
+
// sentence about why it would not mint.
|
|
23496
|
+
evaluation.remedy
|
|
23497
|
+
);
|
|
23498
|
+
return;
|
|
23499
|
+
}
|
|
23500
|
+
if (failure.kind === "contract") {
|
|
23501
|
+
reply.notice(contractFaultText(failure.message));
|
|
23502
|
+
return;
|
|
23503
|
+
}
|
|
23504
|
+
reply.notice(infraWarningText());
|
|
23505
|
+
return;
|
|
23506
|
+
}
|
|
23507
|
+
const telemetryConfig = readTelemetryConfig();
|
|
23508
|
+
const run = buildGuardEvalRun({
|
|
23509
|
+
engagementId: guardEngagementId,
|
|
23510
|
+
runType: "hook",
|
|
22567
23511
|
// The phase this run belongs to, as the client named it — it names the
|
|
22568
23512
|
// guard-eval log file the run is appended to. Absent → the run is unphased.
|
|
22569
23513
|
phase: telemetryConfig.phase,
|
|
@@ -22597,10 +23541,8 @@ ${notRun}` : "";
|
|
|
22597
23541
|
return;
|
|
22598
23542
|
}
|
|
22599
23543
|
if (envelope.blocking) {
|
|
22600
|
-
const
|
|
22601
|
-
|
|
22602
|
-
${guardLines}${notRunBlock}`;
|
|
22603
|
-
reply.block(reason);
|
|
23544
|
+
const reason = `${blockReason(envelope.guardsFired.map((g) => g.explanation))}${notRunBlock}`;
|
|
23545
|
+
reply.block(reason, stoppedMessage(blockingExplanations(envelope.guardsFired)));
|
|
22604
23546
|
} else {
|
|
22605
23547
|
const guardLines = envelope.guardsFired.map((g) => ` [${g.severity.toUpperCase()}] ${g.patternRef}: ${g.explanation}`).join("\n");
|
|
22606
23548
|
reply.notice(`[Halfcycle] Guard warning: ${envelope.explanation}
|
|
@@ -22608,17 +23550,17 @@ ${guardLines}${notRunBlock}`);
|
|
|
22608
23550
|
}
|
|
22609
23551
|
}
|
|
22610
23552
|
function readStdin() {
|
|
22611
|
-
return new Promise((
|
|
23553
|
+
return new Promise((resolve8, reject) => {
|
|
22612
23554
|
const chunks = [];
|
|
22613
23555
|
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
22614
|
-
process.stdin.on("end", () =>
|
|
23556
|
+
process.stdin.on("end", () => resolve8(Buffer.concat(chunks).toString("utf-8")));
|
|
22615
23557
|
process.stdin.on("error", reject);
|
|
22616
23558
|
});
|
|
22617
23559
|
}
|
|
22618
23560
|
|
|
22619
23561
|
// dist/liveness.js
|
|
22620
|
-
import { closeSync, mkdtempSync, openSync, readdirSync as readdirSync5, readFileSync as
|
|
22621
|
-
import { join as
|
|
23562
|
+
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";
|
|
23563
|
+
import { join as join11 } from "node:path";
|
|
22622
23564
|
var LIVE_WINDOW_MS = 10 * 60 * 1e3;
|
|
22623
23565
|
function field(input, key) {
|
|
22624
23566
|
if (typeof input !== "object" || input === null)
|
|
@@ -22627,10 +23569,10 @@ function field(input, key) {
|
|
|
22627
23569
|
return typeof v === "string" && v !== "" ? v : null;
|
|
22628
23570
|
}
|
|
22629
23571
|
function indexEntry(dir, sessionId, name) {
|
|
22630
|
-
const folder =
|
|
23572
|
+
const folder = join11(dir, livenessIndexDirName(sessionId));
|
|
22631
23573
|
for (let attempt2 = 0; attempt2 < 3; attempt2++) {
|
|
22632
23574
|
try {
|
|
22633
|
-
|
|
23575
|
+
closeSync2(openSync2(join11(folder, name), "a", 384));
|
|
22634
23576
|
return true;
|
|
22635
23577
|
} catch (err) {
|
|
22636
23578
|
if (err.code !== "ENOENT")
|
|
@@ -22641,10 +23583,10 @@ function indexEntry(dir, sessionId, name) {
|
|
|
22641
23583
|
tmp = mkdtempSync(`${folder}.`);
|
|
22642
23584
|
for (const existing of readdirSync5(dir)) {
|
|
22643
23585
|
if (parseLivenessFileName(existing)?.sessionId === sessionId)
|
|
22644
|
-
|
|
23586
|
+
closeSync2(openSync2(join11(tmp, existing), "a", 384));
|
|
22645
23587
|
}
|
|
22646
|
-
|
|
22647
|
-
|
|
23588
|
+
closeSync2(openSync2(join11(tmp, name), "a", 384));
|
|
23589
|
+
renameSync5(tmp, folder);
|
|
22648
23590
|
return true;
|
|
22649
23591
|
} catch {
|
|
22650
23592
|
if (tmp !== null)
|
|
@@ -22669,18 +23611,18 @@ function recordLiveness(input, deps = {}) {
|
|
|
22669
23611
|
const name = livenessFileName(livenessKey(copy?.common ?? null), sessionId, agentId);
|
|
22670
23612
|
if (!indexEntry(dir, sessionId, name))
|
|
22671
23613
|
return null;
|
|
22672
|
-
const path =
|
|
23614
|
+
const path = join11(dir, name);
|
|
22673
23615
|
const text = `${cwd}
|
|
22674
23616
|
${copy?.top ?? ""}
|
|
22675
23617
|
`;
|
|
22676
23618
|
try {
|
|
22677
|
-
|
|
23619
|
+
writeFileSync7(path, text, { encoding: "utf-8", flag: "wx", mode: 384 });
|
|
22678
23620
|
return { firstSeen: true };
|
|
22679
23621
|
} catch (err) {
|
|
22680
23622
|
if (err.code !== "EEXIST")
|
|
22681
23623
|
return null;
|
|
22682
23624
|
}
|
|
22683
|
-
|
|
23625
|
+
writeFileSync7(path, text, { encoding: "utf-8", mode: 384 });
|
|
22684
23626
|
return { firstSeen: false };
|
|
22685
23627
|
} catch {
|
|
22686
23628
|
return null;
|
|
@@ -22699,7 +23641,7 @@ function clearLiveness(input, deps = {}) {
|
|
|
22699
23641
|
if (p === null || p.sessionId !== sessionId || p.agentId !== agentId)
|
|
22700
23642
|
continue;
|
|
22701
23643
|
try {
|
|
22702
|
-
|
|
23644
|
+
unlinkSync6(join11(dir, name));
|
|
22703
23645
|
removed += 1;
|
|
22704
23646
|
} catch {
|
|
22705
23647
|
}
|
|
@@ -22712,7 +23654,7 @@ function clearLiveness(input, deps = {}) {
|
|
|
22712
23654
|
}
|
|
22713
23655
|
function topOf(path) {
|
|
22714
23656
|
try {
|
|
22715
|
-
const top =
|
|
23657
|
+
const top = readFileSync14(path, "utf-8").split("\n")[1] ?? "";
|
|
22716
23658
|
return top === "" ? null : top;
|
|
22717
23659
|
} catch {
|
|
22718
23660
|
return null;
|
|
@@ -22734,10 +23676,10 @@ function readLiveness(sessionId, cwd, deps = {}) {
|
|
|
22734
23676
|
const p = parseLivenessFileName(name);
|
|
22735
23677
|
if (p === null || p.key !== key || p.sessionId !== sessionId)
|
|
22736
23678
|
continue;
|
|
22737
|
-
const full =
|
|
23679
|
+
const full = join11(dir, name);
|
|
22738
23680
|
let seenAt;
|
|
22739
23681
|
try {
|
|
22740
|
-
seenAt =
|
|
23682
|
+
seenAt = statSync4(full).mtimeMs;
|
|
22741
23683
|
} catch {
|
|
22742
23684
|
continue;
|
|
22743
23685
|
}
|
|
@@ -22775,17 +23717,17 @@ function runsBesideOtherWork(toolInput, record2) {
|
|
|
22775
23717
|
}
|
|
22776
23718
|
|
|
22777
23719
|
// dist/pace-hook.js
|
|
22778
|
-
import { mkdirSync as mkdirSync7, readFileSync as
|
|
23720
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync18, renameSync as renameSync9, unlinkSync as unlinkSync8, writeFileSync as writeFileSync11 } from "node:fs";
|
|
22779
23721
|
import nodePath4 from "node:path";
|
|
22780
23722
|
|
|
22781
23723
|
// dist/pace-verb.js
|
|
22782
|
-
import { mkdirSync as mkdirSync6, readdirSync as readdirSync7, readFileSync as
|
|
23724
|
+
import { mkdirSync as mkdirSync6, readdirSync as readdirSync7, readFileSync as readFileSync17, renameSync as renameSync8, statSync as statSync6, writeFileSync as writeFileSync10 } from "node:fs";
|
|
22783
23725
|
import { homedir as homedir6 } from "node:os";
|
|
22784
23726
|
import nodePath3 from "node:path";
|
|
22785
23727
|
|
|
22786
23728
|
// dist/pacing.js
|
|
22787
|
-
import { createHash as
|
|
22788
|
-
import { closeSync as
|
|
23729
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
23730
|
+
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
23731
|
import { homedir as homedir5 } from "node:os";
|
|
22790
23732
|
import nodePath from "node:path";
|
|
22791
23733
|
var DEFAULT_CONFIG_FOLDER_NAME = ".claude";
|
|
@@ -22834,7 +23776,7 @@ function pacingDir(home) {
|
|
|
22834
23776
|
}
|
|
22835
23777
|
function readJsonObject(path) {
|
|
22836
23778
|
try {
|
|
22837
|
-
const v = JSON.parse(
|
|
23779
|
+
const v = JSON.parse(readFileSync15(path, "utf-8"));
|
|
22838
23780
|
return typeof v === "object" && v !== null && !Array.isArray(v) ? v : null;
|
|
22839
23781
|
} catch {
|
|
22840
23782
|
return null;
|
|
@@ -22842,9 +23784,9 @@ function readJsonObject(path) {
|
|
|
22842
23784
|
}
|
|
22843
23785
|
function writeJsonWhole(path, value) {
|
|
22844
23786
|
mkdirSync4(nodePath.dirname(path), { recursive: true, mode: 448 });
|
|
22845
|
-
const tmp = `${path}.${String(process.pid)}.${
|
|
22846
|
-
|
|
22847
|
-
|
|
23787
|
+
const tmp = `${path}.${String(process.pid)}.${createHash4("sha1").update(String(Math.random())).digest("hex").slice(0, 8)}.tmp`;
|
|
23788
|
+
writeFileSync8(tmp, JSON.stringify(value), { mode: 384 });
|
|
23789
|
+
renameSync6(tmp, path);
|
|
22848
23790
|
}
|
|
22849
23791
|
function writeAccountEntry(path, key, entry) {
|
|
22850
23792
|
const current = readJsonObject(path);
|
|
@@ -22864,6 +23806,62 @@ function accountKeysOf(path) {
|
|
|
22864
23806
|
const accounts = current?.["accounts"];
|
|
22865
23807
|
return typeof accounts === "object" && accounts !== null && !Array.isArray(accounts) ? Object.keys(accounts) : [];
|
|
22866
23808
|
}
|
|
23809
|
+
var PACE_STATE_FILENAME = "state.json";
|
|
23810
|
+
function paceStateEntryOf(v) {
|
|
23811
|
+
if (typeof v !== "object" || v === null || Array.isArray(v))
|
|
23812
|
+
return null;
|
|
23813
|
+
const o = v;
|
|
23814
|
+
const pace = paceStateSchema.safeParse(o["pace"]);
|
|
23815
|
+
const kind = o["windowKind"] === null ? null : paceWindowKindSchema.safeParse(o["windowKind"]);
|
|
23816
|
+
const allowance = o["allowance"];
|
|
23817
|
+
const number4 = o["number"];
|
|
23818
|
+
const until = o["until"];
|
|
23819
|
+
const at = o["at"];
|
|
23820
|
+
if (!pace.success || kind !== null && !kind.success)
|
|
23821
|
+
return null;
|
|
23822
|
+
if (typeof allowance !== "number" || !Number.isInteger(allowance) || allowance < 0)
|
|
23823
|
+
return null;
|
|
23824
|
+
if (typeof number4 !== "number" || !Number.isInteger(number4) || number4 < 1 || number4 > PACE_AGENTS_MAX)
|
|
23825
|
+
return null;
|
|
23826
|
+
if (until !== null && (typeof until !== "string" || Number.isNaN(Date.parse(until))))
|
|
23827
|
+
return null;
|
|
23828
|
+
if (typeof at !== "string" || Number.isNaN(Date.parse(at)))
|
|
23829
|
+
return null;
|
|
23830
|
+
return { pace: pace.data, allowance, number: number4, windowKind: kind === null ? null : kind.data, until, at };
|
|
23831
|
+
}
|
|
23832
|
+
function rememberedBasisOf(v) {
|
|
23833
|
+
if (typeof v !== "object" || v === null || Array.isArray(v))
|
|
23834
|
+
return null;
|
|
23835
|
+
const o = v;
|
|
23836
|
+
const phase = o["phase"] === null ? null : phaseNameSchema.safeParse(o["phase"]);
|
|
23837
|
+
const plan = o["claudePlan"] === null ? null : claudePlanSchema.safeParse(o["claudePlan"]);
|
|
23838
|
+
const keep = keepFreePercentSchema.safeParse(o["keepFreePercent"]);
|
|
23839
|
+
if (phase !== null && !phase.success || plan !== null && !plan.success || !keep.success)
|
|
23840
|
+
return null;
|
|
23841
|
+
return { phase: phase === null ? null : phase.data, claudePlan: plan === null ? null : plan.data, keepFreePercent: keep.data };
|
|
23842
|
+
}
|
|
23843
|
+
function readPaceStateFile(dir) {
|
|
23844
|
+
const o = readJsonObject(nodePath.join(dir, PACE_STATE_FILENAME));
|
|
23845
|
+
const accounts = {};
|
|
23846
|
+
const rawAccounts = o?.["accounts"];
|
|
23847
|
+
if (typeof rawAccounts === "object" && rawAccounts !== null && !Array.isArray(rawAccounts)) {
|
|
23848
|
+
for (const [key, v] of Object.entries(rawAccounts)) {
|
|
23849
|
+
const e = paceStateEntryOf(v);
|
|
23850
|
+
if (e !== null)
|
|
23851
|
+
accounts[key] = e;
|
|
23852
|
+
}
|
|
23853
|
+
}
|
|
23854
|
+
return { accounts, basis: rememberedBasisOf(o?.["basis"]) };
|
|
23855
|
+
}
|
|
23856
|
+
function writePaceState(dir, key, entry, basis) {
|
|
23857
|
+
const current = readPaceStateFile(dir);
|
|
23858
|
+
const accounts = { ...current.accounts };
|
|
23859
|
+
if (entry === null)
|
|
23860
|
+
delete accounts[key];
|
|
23861
|
+
else
|
|
23862
|
+
accounts[key] = entry;
|
|
23863
|
+
writeJsonWhole(nodePath.join(dir, PACE_STATE_FILENAME), { accounts, basis: basis ?? current.basis });
|
|
23864
|
+
}
|
|
22867
23865
|
function recordFactsOf(o) {
|
|
22868
23866
|
if (o["type"] !== "assistant")
|
|
22869
23867
|
return null;
|
|
@@ -22979,7 +23977,7 @@ var NEWLINE = 10;
|
|
|
22979
23977
|
var ASSISTANT = Buffer.from('"assistant"');
|
|
22980
23978
|
var READ_CHUNK = 8 * 1024 * 1024;
|
|
22981
23979
|
function readWholeLines(path, from, size, onLine) {
|
|
22982
|
-
const fd =
|
|
23980
|
+
const fd = openSync3(path, "r");
|
|
22983
23981
|
try {
|
|
22984
23982
|
let pos = from;
|
|
22985
23983
|
let carry = Buffer.alloc(0);
|
|
@@ -23007,11 +24005,11 @@ function readWholeLines(path, from, size, onLine) {
|
|
|
23007
24005
|
}
|
|
23008
24006
|
return consumed;
|
|
23009
24007
|
} finally {
|
|
23010
|
-
|
|
24008
|
+
closeSync3(fd);
|
|
23011
24009
|
}
|
|
23012
24010
|
}
|
|
23013
24011
|
function hashId(id) {
|
|
23014
|
-
return
|
|
24012
|
+
return createHash4("sha1").update(id).digest("base64url").slice(0, 16);
|
|
23015
24013
|
}
|
|
23016
24014
|
function scanRecords(key, deps = {}) {
|
|
23017
24015
|
const nowMs = (deps.now ?? (() => /* @__PURE__ */ new Date()))().getTime();
|
|
@@ -23036,7 +24034,7 @@ function scanRecords(key, deps = {}) {
|
|
|
23036
24034
|
let size;
|
|
23037
24035
|
let mtimeSec;
|
|
23038
24036
|
try {
|
|
23039
|
-
const st =
|
|
24037
|
+
const st = statSync5(file2.path);
|
|
23040
24038
|
size = st.size;
|
|
23041
24039
|
mtimeSec = Math.floor(st.mtimeMs / 1e3);
|
|
23042
24040
|
} catch {
|
|
@@ -23159,7 +24157,7 @@ function tokensBetween(minutes, fromSec, toSec) {
|
|
|
23159
24157
|
}
|
|
23160
24158
|
function resolvedRecords(key, path) {
|
|
23161
24159
|
try {
|
|
23162
|
-
return
|
|
24160
|
+
return realpathSync5(recordsFolderOf(key, path));
|
|
23163
24161
|
} catch {
|
|
23164
24162
|
return null;
|
|
23165
24163
|
}
|
|
@@ -23172,7 +24170,7 @@ function timeLogTranscripts(home) {
|
|
|
23172
24170
|
continue;
|
|
23173
24171
|
let text;
|
|
23174
24172
|
try {
|
|
23175
|
-
text =
|
|
24173
|
+
text = readFileSync15(nodePath.join(engagements, e.name, TIME_LOG_DIR_NAME, TIME_LOG_FILENAME), "utf-8");
|
|
23176
24174
|
} catch {
|
|
23177
24175
|
continue;
|
|
23178
24176
|
}
|
|
@@ -23332,7 +24330,7 @@ function knownWindowOf(learned, reported, now) {
|
|
|
23332
24330
|
|
|
23333
24331
|
// dist/scan-lock.js
|
|
23334
24332
|
import { spawn } from "node:child_process";
|
|
23335
|
-
import { linkSync, mkdirSync as mkdirSync5, readFileSync as
|
|
24333
|
+
import { linkSync, mkdirSync as mkdirSync5, readFileSync as readFileSync16, renameSync as renameSync7, unlinkSync as unlinkSync7, writeFileSync as writeFileSync9 } from "node:fs";
|
|
23336
24334
|
import nodePath2 from "node:path";
|
|
23337
24335
|
var SCAN_LOCK_FILENAME = "scan.lock";
|
|
23338
24336
|
var SCAN_LOCK_STALE_MS = 15 * 60 * 1e3;
|
|
@@ -23343,7 +24341,7 @@ function scanLockPath(home) {
|
|
|
23343
24341
|
function readLockBody(path) {
|
|
23344
24342
|
let text;
|
|
23345
24343
|
try {
|
|
23346
|
-
text =
|
|
24344
|
+
text = readFileSync16(path, "utf-8");
|
|
23347
24345
|
} catch (err) {
|
|
23348
24346
|
return err.code === "ENOENT" ? "missing" : null;
|
|
23349
24347
|
}
|
|
@@ -23373,7 +24371,7 @@ function isStale(body, staleMs, deps) {
|
|
|
23373
24371
|
return !(deps.alive ?? processAlive)(body.pid);
|
|
23374
24372
|
}
|
|
23375
24373
|
function writeLock(path, pid, deps, flag) {
|
|
23376
|
-
|
|
24374
|
+
writeFileSync9(path, JSON.stringify({ pid, at: (deps.clock ?? Date.now)() }), { flag, mode: 384 });
|
|
23377
24375
|
}
|
|
23378
24376
|
function tryTakeScanLock(deps = {}) {
|
|
23379
24377
|
return tryTakeLockFile(scanLockPath(deps.home), SCAN_LOCK_STALE_MS, deps);
|
|
@@ -23400,7 +24398,7 @@ function tryTakeLockFile(path, staleMs, deps = {}) {
|
|
|
23400
24398
|
return false;
|
|
23401
24399
|
const aside = `${path}.${String(pid)}.${String((deps.clock ?? Date.now)())}.stale`;
|
|
23402
24400
|
try {
|
|
23403
|
-
|
|
24401
|
+
renameSync7(path, aside);
|
|
23404
24402
|
} catch {
|
|
23405
24403
|
continue;
|
|
23406
24404
|
}
|
|
@@ -23412,13 +24410,13 @@ function tryTakeLockFile(path, staleMs, deps = {}) {
|
|
|
23412
24410
|
} catch {
|
|
23413
24411
|
}
|
|
23414
24412
|
try {
|
|
23415
|
-
|
|
24413
|
+
unlinkSync7(aside);
|
|
23416
24414
|
} catch {
|
|
23417
24415
|
}
|
|
23418
24416
|
return false;
|
|
23419
24417
|
}
|
|
23420
24418
|
try {
|
|
23421
|
-
|
|
24419
|
+
unlinkSync7(aside);
|
|
23422
24420
|
} catch {
|
|
23423
24421
|
}
|
|
23424
24422
|
}
|
|
@@ -23429,7 +24427,7 @@ function releaseLockFile(path, pid = process.pid) {
|
|
|
23429
24427
|
if (body === "missing" || body === null || body.pid !== pid)
|
|
23430
24428
|
return;
|
|
23431
24429
|
try {
|
|
23432
|
-
|
|
24430
|
+
unlinkSync7(path);
|
|
23433
24431
|
} catch {
|
|
23434
24432
|
}
|
|
23435
24433
|
}
|
|
@@ -23620,7 +24618,7 @@ function readBuildNumber(engagementId, home) {
|
|
|
23620
24618
|
if (dir === null)
|
|
23621
24619
|
return null;
|
|
23622
24620
|
try {
|
|
23623
|
-
const v = JSON.parse(
|
|
24621
|
+
const v = JSON.parse(readFileSync17(nodePath3.join(dir, BUILD_NUMBER_FILENAME), "utf-8"));
|
|
23624
24622
|
const n = v["number"];
|
|
23625
24623
|
if (typeof v["phase"] === "string" && typeof n === "number" && Number.isInteger(n) && n >= 1 && n <= PACE_AGENTS_MAX && typeof v["namedByOwner"] === "boolean") {
|
|
23626
24624
|
return { phase: v["phase"], number: n, namedByOwner: v["namedByOwner"] };
|
|
@@ -23640,12 +24638,12 @@ function writeBuildNumber(engagementId, value, home) {
|
|
|
23640
24638
|
mkdirSync6(dir, { recursive: true, mode: 448 });
|
|
23641
24639
|
const path = nodePath3.join(dir, BUILD_NUMBER_FILENAME);
|
|
23642
24640
|
const tmp = `${path}.${String(process.pid)}.tmp`;
|
|
23643
|
-
|
|
23644
|
-
|
|
24641
|
+
writeFileSync10(tmp, JSON.stringify(value), { mode: 384 });
|
|
24642
|
+
renameSync8(tmp, path);
|
|
23645
24643
|
}
|
|
23646
24644
|
function readRememberedClaudePlan(home) {
|
|
23647
24645
|
try {
|
|
23648
|
-
const parsed = claudePlanRequestSchema.safeParse(JSON.parse(
|
|
24646
|
+
const parsed = claudePlanRequestSchema.safeParse(JSON.parse(readFileSync17(nodePath3.join(home ?? homedir6(), HALFCYCLE_DIR_NAME, REMEMBERED_CLAUDE_PLAN_FILENAME), "utf-8")));
|
|
23649
24647
|
return parsed.success ? parsed.data.claudePlan : null;
|
|
23650
24648
|
} catch {
|
|
23651
24649
|
return null;
|
|
@@ -23665,7 +24663,7 @@ function liveSubAgentsIn(cwd, deps = {}) {
|
|
|
23665
24663
|
if (p === null || p.key !== key || p.agentId === null)
|
|
23666
24664
|
continue;
|
|
23667
24665
|
try {
|
|
23668
|
-
if (now -
|
|
24666
|
+
if (now - statSync6(nodePath3.join(dir, name)).mtimeMs <= LIVE_WINDOW_MS)
|
|
23669
24667
|
n += 1;
|
|
23670
24668
|
} catch {
|
|
23671
24669
|
}
|
|
@@ -23824,6 +24822,23 @@ async function runPaceVerb(args2, deps = {}) {
|
|
|
23824
24822
|
number4 = stored?.number ?? Math.min(claudePlanOf(claudePlan).startingAgents, PACE_AGENTS_MAX);
|
|
23825
24823
|
}
|
|
23826
24824
|
const decision = paceOf({ number: number4, keepFreePercent, learned, reported, now });
|
|
24825
|
+
if (basis !== null) {
|
|
24826
|
+
const dir = enginePacingDir(engagementId, deps.home);
|
|
24827
|
+
if (dir !== null) {
|
|
24828
|
+
const entry = {
|
|
24829
|
+
pace: decision.pace,
|
|
24830
|
+
allowance: decision.allowance,
|
|
24831
|
+
number: number4,
|
|
24832
|
+
windowKind: decision.windowKind,
|
|
24833
|
+
until: decision.until === null ? null : decision.until.toISOString(),
|
|
24834
|
+
at: now.toISOString()
|
|
24835
|
+
};
|
|
24836
|
+
try {
|
|
24837
|
+
writePaceState(dir, key, entry, { phase: basis.phase, claudePlan: basis.claudePlan, keepFreePercent });
|
|
24838
|
+
} catch {
|
|
24839
|
+
}
|
|
24840
|
+
}
|
|
24841
|
+
}
|
|
23827
24842
|
const live = liveSubAgentsIn(deps.cwd ?? process.cwd(), deps.live);
|
|
23828
24843
|
const result = {
|
|
23829
24844
|
outcome: "paced",
|
|
@@ -23844,11 +24859,10 @@ async function runPaceVerb(args2, deps = {}) {
|
|
|
23844
24859
|
var PACE_HELD_TOOL = "Agent";
|
|
23845
24860
|
var PACE_STEP_MIN_INTERVAL_MS = OBSERVE_MIN_INTERVAL_MS;
|
|
23846
24861
|
var PACE_STATE_FRESH_MS = 10 * 60 * 1e3;
|
|
23847
|
-
var PACE_PENDING_START_MS =
|
|
24862
|
+
var PACE_PENDING_START_MS = 15 * 1e3;
|
|
23848
24863
|
var PACE_STARTS_LOCK_STALE_MS = 5 * 1e3;
|
|
23849
24864
|
var PACE_STARTS_LOCK_GIVE_UP_MS = 50;
|
|
23850
24865
|
var STARTS_LOCK_POLL_MS = 2;
|
|
23851
|
-
var PACE_STATE_FILENAME = "state.json";
|
|
23852
24866
|
var PACE_STARTS_FILENAME = "starts.json";
|
|
23853
24867
|
var PACE_STARTS_LOCK_FILENAME = "starts.lock";
|
|
23854
24868
|
function objectOf(v) {
|
|
@@ -23856,7 +24870,7 @@ function objectOf(v) {
|
|
|
23856
24870
|
}
|
|
23857
24871
|
function readJson(path) {
|
|
23858
24872
|
try {
|
|
23859
|
-
return objectOf(JSON.parse(
|
|
24873
|
+
return objectOf(JSON.parse(readFileSync18(path, "utf-8")));
|
|
23860
24874
|
} catch {
|
|
23861
24875
|
return null;
|
|
23862
24876
|
}
|
|
@@ -23865,68 +24879,16 @@ function writeJsonWhole2(path, value) {
|
|
|
23865
24879
|
mkdirSync7(nodePath4.dirname(path), { recursive: true, mode: 448 });
|
|
23866
24880
|
const tmp = `${path}.${String(process.pid)}.${Math.random().toString(36).slice(2, 10)}.tmp`;
|
|
23867
24881
|
try {
|
|
23868
|
-
|
|
23869
|
-
|
|
24882
|
+
writeFileSync11(tmp, JSON.stringify(value), { mode: 384 });
|
|
24883
|
+
renameSync9(tmp, path);
|
|
23870
24884
|
} catch (err) {
|
|
23871
24885
|
try {
|
|
23872
|
-
|
|
24886
|
+
unlinkSync8(tmp);
|
|
23873
24887
|
} catch {
|
|
23874
24888
|
}
|
|
23875
24889
|
throw err;
|
|
23876
24890
|
}
|
|
23877
24891
|
}
|
|
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
24892
|
function field2(input, key) {
|
|
23931
24893
|
const v = objectOf(input)?.[key];
|
|
23932
24894
|
return typeof v === "string" && v !== "" ? v : null;
|
|
@@ -23934,12 +24896,14 @@ function field2(input, key) {
|
|
|
23934
24896
|
function shownAs(pace, windowKind, allowance) {
|
|
23935
24897
|
return pace === "full" ? "full" : `${pace}:${windowKind ?? ""}:${pace === "slowing" ? String(allowance) : ""}`;
|
|
23936
24898
|
}
|
|
23937
|
-
function paceChangeText(before, after, working, now) {
|
|
24899
|
+
function paceChangeText(before, after, working, now, keepFreePercent) {
|
|
23938
24900
|
const was = before === null ? "full" : shownAs(before.pace, before.windowKind, before.allowance);
|
|
23939
24901
|
const is = shownAs(after.pace, after.windowKind, after.allowance);
|
|
23940
24902
|
if (was === is)
|
|
23941
24903
|
return null;
|
|
23942
24904
|
if (after.pace === "full") {
|
|
24905
|
+
if (keepFreePercent === 0)
|
|
24906
|
+
return null;
|
|
23943
24907
|
return before !== null && before.windowKind !== null ? paceS3Of(before.windowKind, after.number) : null;
|
|
23944
24908
|
}
|
|
23945
24909
|
if (after.windowKind === null || after.until === null)
|
|
@@ -24009,7 +24973,7 @@ async function paceAtTurnEnd(input, reply, deps = {}) {
|
|
|
24009
24973
|
};
|
|
24010
24974
|
write(dir, key, after, read !== null ? basis : null);
|
|
24011
24975
|
const working = liveCount(readLiveness(field2(input, "session_id"), field2(input, "cwd") ?? process.cwd(), deps.liveness));
|
|
24012
|
-
const text = paceChangeText(before, after, working, now);
|
|
24976
|
+
const text = paceChangeText(before, after, working, now, basis.keepFreePercent);
|
|
24013
24977
|
if (text !== null)
|
|
24014
24978
|
reply.notice(text);
|
|
24015
24979
|
} catch {
|
|
@@ -24138,9 +25102,9 @@ function paceHoldText(input, toolInput, deps = {}) {
|
|
|
24138
25102
|
}
|
|
24139
25103
|
|
|
24140
25104
|
// dist/risky/facts.js
|
|
24141
|
-
import { existsSync as
|
|
25105
|
+
import { existsSync as existsSync7, lstatSync as lstatSync2, readdirSync as readdirSync8, readFileSync as readFileSync19, realpathSync as realpathSync6, statSync as statSync7 } from "node:fs";
|
|
24142
25106
|
import { homedir as homedir7, tmpdir as tmpdir2 } from "node:os";
|
|
24143
|
-
import { basename as pathBasename, dirname as
|
|
25107
|
+
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
25108
|
|
|
24145
25109
|
// dist/risky/shell.js
|
|
24146
25110
|
var MAX_DEPTH = 8;
|
|
@@ -24515,7 +25479,7 @@ var Splitter = class _Splitter {
|
|
|
24515
25479
|
const script = innerScript(words);
|
|
24516
25480
|
if (script !== void 0) {
|
|
24517
25481
|
if (this.depth < MAX_DEPTH) {
|
|
24518
|
-
this.child(script,
|
|
25482
|
+
this.child(script, basename2(words[0].text) === "eval").parseList(0, null);
|
|
24519
25483
|
}
|
|
24520
25484
|
return;
|
|
24521
25485
|
}
|
|
@@ -24584,7 +25548,7 @@ function unwrap(input) {
|
|
|
24584
25548
|
words = words.slice(1);
|
|
24585
25549
|
continue;
|
|
24586
25550
|
}
|
|
24587
|
-
const prog =
|
|
25551
|
+
const prog = basename2(name);
|
|
24588
25552
|
const valueOptions = WRAPPER_VALUE_OPTIONS[prog];
|
|
24589
25553
|
if (valueOptions === void 0)
|
|
24590
25554
|
return words;
|
|
@@ -24628,7 +25592,7 @@ function unwrap(input) {
|
|
|
24628
25592
|
}
|
|
24629
25593
|
var SHELLS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "ksh"]);
|
|
24630
25594
|
function innerScript(words) {
|
|
24631
|
-
const prog =
|
|
25595
|
+
const prog = basename2(words[0].text);
|
|
24632
25596
|
if (prog === EVAL_PROGRAM)
|
|
24633
25597
|
return words.slice(1).map((w) => w.text).join(" ");
|
|
24634
25598
|
if (!SHELLS.has(prog))
|
|
@@ -24663,13 +25627,13 @@ function innerScript(words) {
|
|
|
24663
25627
|
}
|
|
24664
25628
|
function readsScriptFromInput(raw) {
|
|
24665
25629
|
const words = unwrap(raw);
|
|
24666
|
-
if (words.length === 0 || !SHELLS.has(
|
|
25630
|
+
if (words.length === 0 || !SHELLS.has(basename2(words[0].text)))
|
|
24667
25631
|
return false;
|
|
24668
25632
|
if (innerScript(words) !== void 0)
|
|
24669
25633
|
return false;
|
|
24670
25634
|
return words.slice(1).every((w) => w.text.startsWith("-") || w.text.startsWith("+"));
|
|
24671
25635
|
}
|
|
24672
|
-
function
|
|
25636
|
+
function basename2(word) {
|
|
24673
25637
|
const k = Math.max(word.lastIndexOf("/"), word.lastIndexOf("\\"));
|
|
24674
25638
|
return k < 0 ? word : word.slice(k + 1);
|
|
24675
25639
|
}
|
|
@@ -24684,7 +25648,7 @@ function namesNeverFollowed(line2, commands) {
|
|
|
24684
25648
|
const sourced = commands.some((c) => c.words[0] !== void 0 && (c.words[0].text === "source" || c.words[0].text === "."));
|
|
24685
25649
|
const evaluated = commands.some((c) => c.inPlace === true);
|
|
24686
25650
|
const indirect = commands.some((c) => {
|
|
24687
|
-
const prog = c.words[0] === void 0 ? "" :
|
|
25651
|
+
const prog = c.words[0] === void 0 ? "" : basename2(c.words[0].text);
|
|
24688
25652
|
return prog === "trap" || prog === "builtin" || REFERENCE_BINDERS.has(prog) && c.words.slice(1).some((w) => /^-[A-Za-z]*n/.test(w.text));
|
|
24689
25653
|
});
|
|
24690
25654
|
const fns = functionsIn(line2);
|
|
@@ -24708,7 +25672,7 @@ function namesNeverFollowed(line2, commands) {
|
|
|
24708
25672
|
out.add(hit[1]);
|
|
24709
25673
|
}
|
|
24710
25674
|
}
|
|
24711
|
-
const prog = c.words[0] === void 0 ? "" :
|
|
25675
|
+
const prog = c.words[0] === void 0 ? "" : basename2(c.words[0].text);
|
|
24712
25676
|
const args2 = c.words.slice(1).map((w) => w.text);
|
|
24713
25677
|
if (BINDERS.has(prog) || prog === "unset") {
|
|
24714
25678
|
for (const a of args2) {
|
|
@@ -24933,7 +25897,7 @@ var LineVariables = class {
|
|
|
24933
25897
|
}
|
|
24934
25898
|
}
|
|
24935
25899
|
update(cmd, before) {
|
|
24936
|
-
const prog = cmd.words[0] === void 0 ? "" :
|
|
25900
|
+
const prog = cmd.words[0] === void 0 ? "" : basename2(cmd.words[0].text);
|
|
24937
25901
|
let settings = [];
|
|
24938
25902
|
if (cmd.assigns !== void 0)
|
|
24939
25903
|
settings = cmd.assigns;
|
|
@@ -25127,7 +26091,7 @@ function expandWord(word, vars) {
|
|
|
25127
26091
|
return { text: v.text, literal: !pattern };
|
|
25128
26092
|
}
|
|
25129
26093
|
function expandCommand(cmd, vars) {
|
|
25130
|
-
const first = cmd.words[0] === void 0 ? "" :
|
|
26094
|
+
const first = cmd.words[0] === void 0 ? "" : basename2(cmd.words[0].text);
|
|
25131
26095
|
const keepEmpty = FOLDER_CHANGERS.has(first);
|
|
25132
26096
|
const words = [];
|
|
25133
26097
|
cmd.words.forEach((w, i) => {
|
|
@@ -25171,7 +26135,7 @@ function pathsMade(cmd) {
|
|
|
25171
26135
|
const words = cmd.words;
|
|
25172
26136
|
if (words.length === 0)
|
|
25173
26137
|
return [];
|
|
25174
|
-
const prog =
|
|
26138
|
+
const prog = basename2(words[0].text);
|
|
25175
26139
|
const args2 = words.slice(1);
|
|
25176
26140
|
if (prog === "mkdir") {
|
|
25177
26141
|
const out = [];
|
|
@@ -25211,7 +26175,7 @@ function pathsMade(cmd) {
|
|
|
25211
26175
|
if (ops.length >= 2)
|
|
25212
26176
|
return [{ word: ops[ops.length - 1], by: "clone" }];
|
|
25213
26177
|
if (ops.length === 1 && ops[0].literal) {
|
|
25214
|
-
const name =
|
|
26178
|
+
const name = basename2(ops[0].text.replace(/\/+$/, "")).replace(/\.git$/, "");
|
|
25215
26179
|
if (name !== "" && !name.includes(":"))
|
|
25216
26180
|
return [{ word: { text: name, literal: true }, by: "clone" }];
|
|
25217
26181
|
}
|
|
@@ -25224,24 +26188,6 @@ function pathsMade(cmd) {
|
|
|
25224
26188
|
return [];
|
|
25225
26189
|
}
|
|
25226
26190
|
|
|
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
26191
|
// dist/risky/recognise.js
|
|
25246
26192
|
function madeKey(dirs, word) {
|
|
25247
26193
|
return [...dirs.map((d) => d.text), word.text].join("\0");
|
|
@@ -25405,7 +26351,7 @@ function surelyRuns(simple, before) {
|
|
|
25405
26351
|
}
|
|
25406
26352
|
function folderAfter(prev, simple, before) {
|
|
25407
26353
|
const first = simple.words[0];
|
|
25408
|
-
if (first === void 0 || !FOLDER_CHANGERS.has(
|
|
26354
|
+
if (first === void 0 || !FOLDER_CHANGERS.has(basename2(first.text)))
|
|
25409
26355
|
return prev;
|
|
25410
26356
|
const dirs = prev?.dirs ?? [];
|
|
25411
26357
|
const unknown2 = { dirs, known: false };
|
|
@@ -25414,7 +26360,7 @@ function folderAfter(prev, simple, before) {
|
|
|
25414
26360
|
return prev;
|
|
25415
26361
|
if (simple.next === "||" || before === "||")
|
|
25416
26362
|
return unknown2;
|
|
25417
|
-
const prog =
|
|
26363
|
+
const prog = basename2(first.text);
|
|
25418
26364
|
if (simple.inPlace === true || simple.conditional === true || prog === "popd")
|
|
25419
26365
|
return unknown2;
|
|
25420
26366
|
let args2 = simple.words.slice(1);
|
|
@@ -25458,7 +26404,7 @@ function recogniseSimple(cmd) {
|
|
|
25458
26404
|
}
|
|
25459
26405
|
function programForms(words) {
|
|
25460
26406
|
const forms = [words];
|
|
25461
|
-
const prog =
|
|
26407
|
+
const prog = basename2(words[0].text);
|
|
25462
26408
|
let rest;
|
|
25463
26409
|
if (prog === "npx" || prog === "bunx" || prog === "pnpx") {
|
|
25464
26410
|
rest = skipRunnerOptions(words.slice(1));
|
|
@@ -25504,7 +26450,7 @@ var PROGRAM_MATCHERS = /* @__PURE__ */ new Map([
|
|
|
25504
26450
|
["git", (cmd, args2) => gitMatch(cmd, args2)]
|
|
25505
26451
|
]);
|
|
25506
26452
|
function recogniseWords(cmd, words) {
|
|
25507
|
-
const prog =
|
|
26453
|
+
const prog = basename2(words[0].text);
|
|
25508
26454
|
const args2 = words.slice(1);
|
|
25509
26455
|
const own = PROGRAM_MATCHERS.get(prog);
|
|
25510
26456
|
if (own !== void 0)
|
|
@@ -25585,7 +26531,7 @@ function findMatch(cmd, args2) {
|
|
|
25585
26531
|
if (t === FIND_DELETE_ACTION)
|
|
25586
26532
|
deletes = true;
|
|
25587
26533
|
if ((t === "-exec" || t === "-execdir" || t === "-ok" || t === "-okdir") && i + 1 < args2.length) {
|
|
25588
|
-
const inner =
|
|
26534
|
+
const inner = basename2(args2[i + 1].text);
|
|
25589
26535
|
const viaGit = inner === "git" && args2[i + 2]?.text === "rm";
|
|
25590
26536
|
if (FIND_DELETERS.has(inner) || viaGit) {
|
|
25591
26537
|
deletes = true;
|
|
@@ -25633,8 +26579,9 @@ function findFilterOf(expr) {
|
|
|
25633
26579
|
return names.length === 0 && excluded.length === 0 ? void 0 : { names, excluded };
|
|
25634
26580
|
}
|
|
25635
26581
|
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
|
|
26582
|
+
function gitArgsOf(args2) {
|
|
25637
26583
|
const dirs = [];
|
|
26584
|
+
const config2 = [];
|
|
25638
26585
|
let gitDir;
|
|
25639
26586
|
let workTree;
|
|
25640
26587
|
let i = 0;
|
|
@@ -25660,11 +26607,92 @@ function gitMatch(cmd, args2) {
|
|
|
25660
26607
|
gitDir = value;
|
|
25661
26608
|
if (name === "--work-tree" && value)
|
|
25662
26609
|
workTree = value;
|
|
26610
|
+
if ((name === "-c" || name === "--config-env") && value)
|
|
26611
|
+
config2.push({ word: value, fromEnv: name === "--config-env" });
|
|
26612
|
+
}
|
|
26613
|
+
return { git: { dirs, ...gitDir ? { gitDir } : {}, ...workTree ? { workTree } : {} }, at: i, config: config2 };
|
|
26614
|
+
}
|
|
26615
|
+
function gitMatch(cmd, args2) {
|
|
26616
|
+
const { git: git2, at, config: config2 } = gitArgsOf(args2);
|
|
26617
|
+
const sub = args2[at]?.text;
|
|
26618
|
+
const own = sub === void 0 ? void 0 : GIT_SUBCOMMAND_MATCHERS.get(sub);
|
|
26619
|
+
return own === void 0 ? [] : own(cmd, args2.slice(at + 1), git2, config2);
|
|
26620
|
+
}
|
|
26621
|
+
var PUSH_ALL_REFS_OPTION = "--mirror";
|
|
26622
|
+
function hasLongAsGitReads(args2, name) {
|
|
26623
|
+
for (const a of args2) {
|
|
26624
|
+
if (a.text === "--")
|
|
26625
|
+
return false;
|
|
26626
|
+
const spelled = a.text.split("=")[0];
|
|
26627
|
+
if (spelled.length >= 3 && spelled.startsWith("--") && name.startsWith(spelled))
|
|
26628
|
+
return true;
|
|
26629
|
+
}
|
|
26630
|
+
return false;
|
|
26631
|
+
}
|
|
26632
|
+
function configNameOf(c) {
|
|
26633
|
+
const t = c.word.text;
|
|
26634
|
+
const eq = t.indexOf("=");
|
|
26635
|
+
return eq < 0 ? t : t.slice(0, eq);
|
|
26636
|
+
}
|
|
26637
|
+
function configValueOf(c) {
|
|
26638
|
+
if (c.fromEnv || !c.word.literal)
|
|
26639
|
+
return null;
|
|
26640
|
+
const t = c.word.text;
|
|
26641
|
+
const eq = t.indexOf("=");
|
|
26642
|
+
return eq < 0 ? true : t.slice(eq + 1);
|
|
26643
|
+
}
|
|
26644
|
+
function configNameUnknown(name) {
|
|
26645
|
+
return /[$`*?[]/.test(name);
|
|
26646
|
+
}
|
|
26647
|
+
var GIT_FALSE = /^(false|no|off|[-+]?0+)?$/i;
|
|
26648
|
+
function configMirrors(config2) {
|
|
26649
|
+
return config2.some((c) => {
|
|
26650
|
+
const name = configNameOf(c);
|
|
26651
|
+
if (configNameUnknown(name))
|
|
26652
|
+
return true;
|
|
26653
|
+
if (!/^remote\..+\.mirror$/i.test(name))
|
|
26654
|
+
return false;
|
|
26655
|
+
const value = configValueOf(c);
|
|
26656
|
+
return value === true || value === null || !GIT_FALSE.test(value);
|
|
26657
|
+
});
|
|
26658
|
+
}
|
|
26659
|
+
function pushMirrors(rest, config2) {
|
|
26660
|
+
return hasLongAsGitReads(rest, PUSH_ALL_REFS_OPTION) || configMirrors(config2);
|
|
26661
|
+
}
|
|
26662
|
+
var SAVED_NAMESPACE = SAVED_REF_PREFIX.split("/").slice(0, 2).join("/") + "/";
|
|
26663
|
+
function refspecSendsSaves(text) {
|
|
26664
|
+
if (text.includes(SAVED_NAMESPACE))
|
|
26665
|
+
return true;
|
|
26666
|
+
const src = text.replace(/^\+/, "").split(":")[0];
|
|
26667
|
+
const star = src.indexOf("*");
|
|
26668
|
+
return star >= 0 && SAVED_NAMESPACE.startsWith(src.slice(0, star));
|
|
26669
|
+
}
|
|
26670
|
+
function configPushSendsSaves(config2) {
|
|
26671
|
+
return config2.some((c) => {
|
|
26672
|
+
const name = configNameOf(c);
|
|
26673
|
+
if (configNameUnknown(name))
|
|
26674
|
+
return true;
|
|
26675
|
+
if (!/^remote\..+\.push$/i.test(name))
|
|
26676
|
+
return false;
|
|
26677
|
+
const value = configValueOf(c);
|
|
26678
|
+
return value === null || value !== true && refspecSendsSaves(value);
|
|
26679
|
+
});
|
|
26680
|
+
}
|
|
26681
|
+
function pushesSavedRefs(command) {
|
|
26682
|
+
for (const cmd of splitCommand(command)) {
|
|
26683
|
+
if (cmd.words.length === 0 || basename2(cmd.words[0].text) !== "git")
|
|
26684
|
+
continue;
|
|
26685
|
+
const args2 = cmd.words.slice(1);
|
|
26686
|
+
const { at, config: config2 } = gitArgsOf(args2);
|
|
26687
|
+
if (args2[at]?.text !== "push")
|
|
26688
|
+
continue;
|
|
26689
|
+
const rest = args2.slice(at + 1);
|
|
26690
|
+
if (pushMirrors(rest, config2) || configPushSendsSaves(config2))
|
|
26691
|
+
return true;
|
|
26692
|
+
if (rest.some((w) => refspecSendsSaves(w.text)))
|
|
26693
|
+
return true;
|
|
25663
26694
|
}
|
|
25664
|
-
|
|
25665
|
-
const sub = args2[i]?.text;
|
|
25666
|
-
const own = sub === void 0 ? void 0 : GIT_SUBCOMMAND_MATCHERS.get(sub);
|
|
25667
|
-
return own === void 0 ? [] : own(cmd, args2.slice(i + 1), git2);
|
|
26695
|
+
return false;
|
|
25668
26696
|
}
|
|
25669
26697
|
var GIT_SUBCOMMAND_MATCHERS = /* @__PURE__ */ new Map([
|
|
25670
26698
|
["rm", (cmd, rest, git2) => gitRm(cmd, rest, git2)],
|
|
@@ -25675,7 +26703,7 @@ var GIT_SUBCOMMAND_MATCHERS = /* @__PURE__ */ new Map([
|
|
|
25675
26703
|
["clean", (cmd, rest, git2) => gitClean(cmd, rest, git2)],
|
|
25676
26704
|
["stash", (cmd, rest, git2) => gitStash(cmd, rest, git2)],
|
|
25677
26705
|
["worktree", (cmd, rest, git2) => gitWorktree(cmd, rest, git2)],
|
|
25678
|
-
["push", (cmd, rest, git2) => gitPush(cmd, rest, git2)],
|
|
26706
|
+
["push", (cmd, rest, git2, config2) => gitPush(cmd, rest, git2, config2)],
|
|
25679
26707
|
["branch", (cmd, rest, git2) => gitBranch(cmd, rest, git2)]
|
|
25680
26708
|
]);
|
|
25681
26709
|
function gitSwitch(cmd, rest, git2) {
|
|
@@ -25753,7 +26781,7 @@ function gitWorktree(cmd, rest, git2) {
|
|
|
25753
26781
|
return [{ type: "discard-unsaved", form: "worktree-remove", paths: operands(args2), git: git2, command: cmd }];
|
|
25754
26782
|
}
|
|
25755
26783
|
var PUSH_VALUE_OPTIONS = /* @__PURE__ */ new Set(["-o", "--push-option", "--repo", "--receive-pack", "--exec"]);
|
|
25756
|
-
function gitPush(cmd, rest, git2) {
|
|
26784
|
+
function gitPush(cmd, rest, git2, config2) {
|
|
25757
26785
|
if (hasShort(rest, "n") || hasLong(rest, "--dry-run"))
|
|
25758
26786
|
return [];
|
|
25759
26787
|
const ops = operands(rest, PUSH_VALUE_OPTIONS);
|
|
@@ -25762,7 +26790,7 @@ function gitPush(cmd, rest, git2) {
|
|
|
25762
26790
|
const force = hasShort(rest, "f") || hasLong(rest, "--force");
|
|
25763
26791
|
const forceWithLease = hasLong(rest, "--force-with-lease");
|
|
25764
26792
|
const forceIfIncludes = hasLong(rest, "--force-if-includes");
|
|
25765
|
-
const mirror =
|
|
26793
|
+
const mirror = pushMirrors(rest, config2);
|
|
25766
26794
|
const deletes = hasShort(rest, "d") || hasLong(rest, "--delete") || refspecs.some((r) => r.text.startsWith(":") && r.text.length > 1);
|
|
25767
26795
|
const plusRefspec = refspecs.some((r) => r.text.startsWith("+"));
|
|
25768
26796
|
if (!(force || forceWithLease || forceIfIncludes || mirror || deletes || plusRefspec))
|
|
@@ -25802,7 +26830,7 @@ function wipeDataMatch(cmd, prog, args2, words) {
|
|
|
25802
26830
|
const execAt = args2.findIndex((a) => a.text === "exec" || a.text === "run");
|
|
25803
26831
|
if (execAt >= 0) {
|
|
25804
26832
|
for (let i = execAt + 1; i < args2.length; i++) {
|
|
25805
|
-
const inner =
|
|
26833
|
+
const inner = basename2(args2[i].text);
|
|
25806
26834
|
if (SQL_CLIENTS.has(inner) || inner === "dropdb" || inner === "redis-cli") {
|
|
25807
26835
|
return wipeDataMatch(cmd, inner, args2.slice(i + 1), args2.slice(i));
|
|
25808
26836
|
}
|
|
@@ -26149,7 +27177,7 @@ function shellRecordMatches(cmd, found2) {
|
|
|
26149
27177
|
});
|
|
26150
27178
|
}
|
|
26151
27179
|
const words = cmd.words;
|
|
26152
|
-
const prog = words.length > 0 ?
|
|
27180
|
+
const prog = words.length > 0 ? basename2(words[0].text) : "";
|
|
26153
27181
|
const args2 = words.slice(1);
|
|
26154
27182
|
if (prog === TRUNCATE_PROGRAM) {
|
|
26155
27183
|
const targets = operands(args2, /* @__PURE__ */ new Set(["-s", "--size", "-r", "--reference"])).filter((w) => mayBeRecordWord(w));
|
|
@@ -26182,7 +27210,7 @@ function movesStdout(r) {
|
|
|
26182
27210
|
return [">", ">>", ">|", ">&"].includes(r.op) && (r.fd === void 0 || r.fd === "1");
|
|
26183
27211
|
}
|
|
26184
27212
|
function savedObjectShown(words) {
|
|
26185
|
-
if (words.length !== 3 ||
|
|
27213
|
+
if (words.length !== 3 || basename2(words[0].text) !== "git" || words[1].text !== "show" || !words[2].literal)
|
|
26186
27214
|
return void 0;
|
|
26187
27215
|
return /^[0-9a-f]{7,64}:[^:]+$/.test(words[2].text) ? words[2].text : void 0;
|
|
26188
27216
|
}
|
|
@@ -26285,13 +27313,13 @@ var Env = class {
|
|
|
26285
27313
|
description;
|
|
26286
27314
|
projectCommonDir;
|
|
26287
27315
|
constructor(ctx) {
|
|
26288
|
-
this.projectDir = realOrSelf(
|
|
26289
|
-
this.cwd = realOrSelf(
|
|
26290
|
-
this.home = realOrSelf(
|
|
27316
|
+
this.projectDir = realOrSelf(resolve4(ctx.projectDir));
|
|
27317
|
+
this.cwd = realOrSelf(resolve4(this.projectDir, ctx.cwd));
|
|
27318
|
+
this.home = realOrSelf(resolve4(ctx.home ?? homedir7()));
|
|
26291
27319
|
this.timeoutMs = ctx.gitTimeoutMs ?? FACTS_GIT_TIMEOUT_MS;
|
|
26292
|
-
this.tempDir = realOrSelf(
|
|
27320
|
+
this.tempDir = realOrSelf(resolve4(ctx.tempDir ?? tmpdir2()));
|
|
26293
27321
|
const roots = ctx.tempRoots ?? [this.tempDir, tmpdir2(), ...TEMP_ROOTS];
|
|
26294
|
-
this.tempRoots = [...new Set(roots.map((r) => realOrSelf(
|
|
27322
|
+
this.tempRoots = [...new Set(roots.map((r) => realOrSelf(resolve4(r))))].filter((r) => !isInside(this.projectDir, r));
|
|
26295
27323
|
}
|
|
26296
27324
|
isTemp(abs) {
|
|
26297
27325
|
return this.tempRoots.some((r) => isInside(abs, r));
|
|
@@ -26301,7 +27329,7 @@ var Env = class {
|
|
|
26301
27329
|
}
|
|
26302
27330
|
display(abs) {
|
|
26303
27331
|
if (isInside(abs, this.projectDir)) {
|
|
26304
|
-
const rel =
|
|
27332
|
+
const rel = relative5(this.projectDir, abs);
|
|
26305
27333
|
return rel === "" ? "." : toSlash(rel);
|
|
26306
27334
|
}
|
|
26307
27335
|
return toSlash(abs);
|
|
@@ -26311,7 +27339,7 @@ var Env = class {
|
|
|
26311
27339
|
return this.description;
|
|
26312
27340
|
this.description = null;
|
|
26313
27341
|
try {
|
|
26314
|
-
const raw = JSON.parse(
|
|
27342
|
+
const raw = JSON.parse(readFileSync19(join12(repoRoot, SYSTEM_DESCRIPTION_REL), "utf-8"));
|
|
26315
27343
|
const checked = checkSystemDescription(raw);
|
|
26316
27344
|
if (checked.ok)
|
|
26317
27345
|
this.description = checked.description;
|
|
@@ -26342,7 +27370,7 @@ var Env = class {
|
|
|
26342
27370
|
return false;
|
|
26343
27371
|
let dir = abs;
|
|
26344
27372
|
while (!isDirectory(dir)) {
|
|
26345
|
-
const up =
|
|
27373
|
+
const up = dirname6(dir);
|
|
26346
27374
|
if (up === dir)
|
|
26347
27375
|
return false;
|
|
26348
27376
|
dir = up;
|
|
@@ -26354,40 +27382,40 @@ var Env = class {
|
|
|
26354
27382
|
if (!r.ok)
|
|
26355
27383
|
return null;
|
|
26356
27384
|
const d = r.value.trim();
|
|
26357
|
-
return d === "" ? null : realOrSelf(
|
|
27385
|
+
return d === "" ? null : realOrSelf(resolve4(dir, d));
|
|
26358
27386
|
}
|
|
26359
27387
|
};
|
|
26360
27388
|
function realOrSelf(p) {
|
|
26361
27389
|
try {
|
|
26362
|
-
return
|
|
27390
|
+
return realpathSync6(p);
|
|
26363
27391
|
} catch {
|
|
26364
27392
|
return p;
|
|
26365
27393
|
}
|
|
26366
27394
|
}
|
|
26367
27395
|
function toSlash(p) {
|
|
26368
|
-
return
|
|
27396
|
+
return sep3 === "/" ? p : p.split(sep3).join("/");
|
|
26369
27397
|
}
|
|
26370
27398
|
function isInside(child, parent) {
|
|
26371
|
-
const rel =
|
|
26372
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
27399
|
+
const rel = relative5(parent, child);
|
|
27400
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
|
|
26373
27401
|
}
|
|
26374
27402
|
function resolveHome(text, home, base) {
|
|
26375
27403
|
if (text === "~")
|
|
26376
27404
|
return home;
|
|
26377
27405
|
if (text.startsWith("~/"))
|
|
26378
|
-
return
|
|
26379
|
-
return
|
|
27406
|
+
return join12(home, text.slice(2));
|
|
27407
|
+
return resolve4(base, text);
|
|
26380
27408
|
}
|
|
26381
27409
|
function realTarget(abs) {
|
|
26382
|
-
const parent =
|
|
27410
|
+
const parent = dirname6(abs);
|
|
26383
27411
|
if (parent === abs)
|
|
26384
27412
|
return abs;
|
|
26385
|
-
return
|
|
27413
|
+
return join12(realOrSelf(parent), pathBasename(abs));
|
|
26386
27414
|
}
|
|
26387
27415
|
function wordPath(word, base, env) {
|
|
26388
27416
|
if (word.temp !== void 0) {
|
|
26389
|
-
const root = word.temp.base === "mktemp" ?
|
|
26390
|
-
return
|
|
27417
|
+
const root = word.temp.base === "mktemp" ? join12(env.tempDir, ".halfcycle-mktemp-new") : env.tempDir;
|
|
27418
|
+
return resolve4(`${root}${word.temp.suffix}`);
|
|
26391
27419
|
}
|
|
26392
27420
|
return word.literal ? resolveHome(word.text, env.home, base) : null;
|
|
26393
27421
|
}
|
|
@@ -26396,7 +27424,7 @@ function follow(start, prefix, dirs, m, env) {
|
|
|
26396
27424
|
let fresh = start.fresh;
|
|
26397
27425
|
let freshRoot = start.freshRoot;
|
|
26398
27426
|
const seen = [...prefix];
|
|
26399
|
-
const insideFresh = (p) => freshRoot !== void 0 && isInside(
|
|
27427
|
+
const insideFresh = (p) => freshRoot !== void 0 && isInside(resolve4(p), resolve4(freshRoot));
|
|
26400
27428
|
const mark = (by) => {
|
|
26401
27429
|
if (fresh !== void 0 && insideFresh(cwd))
|
|
26402
27430
|
return;
|
|
@@ -26525,14 +27553,14 @@ function expandBraces(text) {
|
|
|
26525
27553
|
return body.split(",").map((b) => `${head}${b}${tail}`);
|
|
26526
27554
|
}
|
|
26527
27555
|
function globPath(abs) {
|
|
26528
|
-
const root = abs.startsWith(
|
|
26529
|
-
const segments = abs.split(
|
|
27556
|
+
const root = abs.startsWith(sep3) ? sep3 : "";
|
|
27557
|
+
const segments = abs.split(sep3).filter((s) => s !== "");
|
|
26530
27558
|
let current = [root === "" ? "." : root];
|
|
26531
27559
|
for (const segment of segments) {
|
|
26532
27560
|
const next = [];
|
|
26533
27561
|
if (!/[*?[]/.test(segment)) {
|
|
26534
27562
|
for (const c of current)
|
|
26535
|
-
next.push(
|
|
27563
|
+
next.push(join12(c, segment));
|
|
26536
27564
|
} else {
|
|
26537
27565
|
const re = segmentPattern(segment);
|
|
26538
27566
|
for (const c of current) {
|
|
@@ -26546,7 +27574,7 @@ function globPath(abs) {
|
|
|
26546
27574
|
if (n.startsWith(".") && !segment.startsWith("."))
|
|
26547
27575
|
continue;
|
|
26548
27576
|
if (re.test(n))
|
|
26549
|
-
next.push(
|
|
27577
|
+
next.push(join12(c, n));
|
|
26550
27578
|
}
|
|
26551
27579
|
}
|
|
26552
27580
|
}
|
|
@@ -26582,7 +27610,7 @@ function segmentPattern(segment) {
|
|
|
26582
27610
|
}
|
|
26583
27611
|
function pathExists(p) {
|
|
26584
27612
|
try {
|
|
26585
|
-
|
|
27613
|
+
lstatSync2(p);
|
|
26586
27614
|
return true;
|
|
26587
27615
|
} catch {
|
|
26588
27616
|
return false;
|
|
@@ -26590,28 +27618,28 @@ function pathExists(p) {
|
|
|
26590
27618
|
}
|
|
26591
27619
|
function isEmptyFolder(p) {
|
|
26592
27620
|
try {
|
|
26593
|
-
return
|
|
27621
|
+
return lstatSync2(p).isDirectory() && readdirSync8(p).length === 0;
|
|
26594
27622
|
} catch {
|
|
26595
27623
|
return false;
|
|
26596
27624
|
}
|
|
26597
27625
|
}
|
|
26598
27626
|
function isFolder(p) {
|
|
26599
27627
|
try {
|
|
26600
|
-
return
|
|
27628
|
+
return statSync7(p).isDirectory();
|
|
26601
27629
|
} catch {
|
|
26602
27630
|
return false;
|
|
26603
27631
|
}
|
|
26604
27632
|
}
|
|
26605
27633
|
function isDirectory(p) {
|
|
26606
27634
|
try {
|
|
26607
|
-
return
|
|
27635
|
+
return lstatSync2(p).isDirectory();
|
|
26608
27636
|
} catch {
|
|
26609
27637
|
return false;
|
|
26610
27638
|
}
|
|
26611
27639
|
}
|
|
26612
|
-
function filesUnder(targets, repoRoot, env, keep) {
|
|
27640
|
+
function filesUnder(targets, repoRoot, env, keep, seen) {
|
|
26613
27641
|
const specs = targets.map((t) => {
|
|
26614
|
-
const rel = toSlash(
|
|
27642
|
+
const rel = toSlash(relative5(repoRoot, t));
|
|
26615
27643
|
return rel === "" ? "." : rel;
|
|
26616
27644
|
});
|
|
26617
27645
|
const tracked = env.git(["--literal-pathspecs", "ls-files", "--full-name", "-z", "--", ...specs], repoRoot);
|
|
@@ -26645,6 +27673,8 @@ function filesUnder(targets, repoRoot, env, keep) {
|
|
|
26645
27673
|
goneFromDisk.add(e.path);
|
|
26646
27674
|
}
|
|
26647
27675
|
}
|
|
27676
|
+
if (seen !== void 0)
|
|
27677
|
+
seen.ignored = ignored.length;
|
|
26648
27678
|
const out = /* @__PURE__ */ new Map();
|
|
26649
27679
|
for (const path of tracked.value.split("\0")) {
|
|
26650
27680
|
if (path === "" || goneFromDisk.has(path))
|
|
@@ -26672,12 +27702,12 @@ function findKeeps(filter, roots, repoRoot) {
|
|
|
26672
27702
|
const names = filter.names.map((n) => findPattern(n.pattern, n.fold, false));
|
|
26673
27703
|
const excluded = filter.excluded.map((p) => findPattern(p, false, true));
|
|
26674
27704
|
const steps = (rel) => {
|
|
26675
|
-
const abs =
|
|
27705
|
+
const abs = join12(repoRoot, rel);
|
|
26676
27706
|
const out = [];
|
|
26677
27707
|
for (const r of roots) {
|
|
26678
27708
|
if (!isInside(abs, r.abs))
|
|
26679
27709
|
continue;
|
|
26680
|
-
const below = toSlash(
|
|
27710
|
+
const below = toSlash(relative5(r.abs, abs)).split("/").filter((x) => x !== "");
|
|
26681
27711
|
const rootName = r.text === null ? pathBasename(r.abs) : r.text.replace(/\/+$/, "").split("/").pop() || r.text;
|
|
26682
27712
|
const prefixes = r.text === null ? [] : [.../* @__PURE__ */ new Set([r.text, r.text.replace(/\/+$/, "") || r.text])];
|
|
26683
27713
|
const chain = [{ name: rootName, printed: r.text === null ? [] : [r.text] }];
|
|
@@ -26726,27 +27756,27 @@ function findPattern(pattern, fold, whole) {
|
|
|
26726
27756
|
return new RegExp(`${re}$`, fold ? "i" : "");
|
|
26727
27757
|
}
|
|
26728
27758
|
function walkRoots(ignoredFolders, specs) {
|
|
26729
|
-
const
|
|
27759
|
+
const under2 = (child, parent) => parent === "." || child === parent || child.startsWith(`${parent}/`);
|
|
26730
27760
|
const roots = /* @__PURE__ */ new Set();
|
|
26731
27761
|
for (const raw of ignoredFolders) {
|
|
26732
27762
|
const folder = raw.replace(/\/+$/, "");
|
|
26733
27763
|
for (const spec of specs) {
|
|
26734
|
-
if (
|
|
27764
|
+
if (under2(folder, spec))
|
|
26735
27765
|
roots.add(folder);
|
|
26736
|
-
else if (
|
|
27766
|
+
else if (under2(spec, folder))
|
|
26737
27767
|
roots.add(spec);
|
|
26738
27768
|
}
|
|
26739
27769
|
}
|
|
26740
27770
|
const all = [...roots];
|
|
26741
|
-
return all.filter((r) => !all.some((o) => o !== r &&
|
|
27771
|
+
return all.filter((r) => !all.some((o) => o !== r && under2(r, o)));
|
|
26742
27772
|
}
|
|
26743
27773
|
function credentialFilesIn(folders, repoRoot, deadline, skip) {
|
|
26744
27774
|
const out = [];
|
|
26745
27775
|
const stack = [];
|
|
26746
27776
|
for (const f of folders) {
|
|
26747
|
-
if (isDirectory(
|
|
27777
|
+
if (isDirectory(join12(repoRoot, f)))
|
|
26748
27778
|
stack.push(f);
|
|
26749
|
-
else if (pathExists(
|
|
27779
|
+
else if (pathExists(join12(repoRoot, f)) && isCredentialPath(f))
|
|
26750
27780
|
out.push(f);
|
|
26751
27781
|
}
|
|
26752
27782
|
let seen = 0;
|
|
@@ -26754,7 +27784,7 @@ function credentialFilesIn(folders, repoRoot, deadline, skip) {
|
|
|
26754
27784
|
const rel = stack.pop();
|
|
26755
27785
|
let entries;
|
|
26756
27786
|
try {
|
|
26757
|
-
entries = readdirSync8(
|
|
27787
|
+
entries = readdirSync8(join12(repoRoot, rel), { withFileTypes: true });
|
|
26758
27788
|
} catch {
|
|
26759
27789
|
continue;
|
|
26760
27790
|
}
|
|
@@ -26851,7 +27881,7 @@ function deleteFilesFacts(m, env) {
|
|
|
26851
27881
|
for (const raw of expanded) {
|
|
26852
27882
|
const abs = realTarget(raw);
|
|
26853
27883
|
written.set(abs, word.literal ? word.text : null);
|
|
26854
|
-
if (abs ===
|
|
27884
|
+
if (abs === sep3 || abs === env.home || abs !== env.projectDir && isInside(env.projectDir, abs)) {
|
|
26855
27885
|
guarded.push(abs);
|
|
26856
27886
|
} else if (isInside(abs, env.projectDir) || !env.isTemp(abs) && env.sameRepository(abs)) {
|
|
26857
27887
|
if (pathExists(abs))
|
|
@@ -26867,16 +27897,18 @@ function deleteFilesFacts(m, env) {
|
|
|
26867
27897
|
const targets = plainFolderRefused ? inside.filter((t) => !isDirectory(t)) : inside;
|
|
26868
27898
|
if (targets.length === 0)
|
|
26869
27899
|
return silentDelete(m);
|
|
26870
|
-
const repoRoot = env.repoRoot(
|
|
27900
|
+
const repoRoot = env.repoRoot(dirname6(targets[0])) ?? env.repoRoot(env.projectDir);
|
|
26871
27901
|
if (repoRoot === null || !targets.every((t) => isInside(t, repoRoot)))
|
|
26872
27902
|
return cannot(thingFor([], targets, null, env));
|
|
26873
27903
|
const keep = m.findFilter === void 0 ? void 0 : findKeeps(m.findFilter, targets.map((t) => ({ abs: t, text: written.get(t) ?? null })), repoRoot);
|
|
26874
|
-
const
|
|
27904
|
+
const seen = { ignored: 0 };
|
|
27905
|
+
const files = filesUnder(targets, repoRoot, env, keep, seen);
|
|
26875
27906
|
if (files === null)
|
|
26876
27907
|
return cannot(thingFor([], targets, repoRoot, env));
|
|
26877
27908
|
const n = files.length;
|
|
26878
27909
|
const k = files.filter((f) => f.neverSaved).length;
|
|
26879
|
-
const
|
|
27910
|
+
const closeFold = m.form !== "find" && seen.ignored === 0 && closeFoldsAll(targets, env);
|
|
27911
|
+
const atStake = m.form === "plain" || closeFold ? k > 0 : n > 0;
|
|
26880
27912
|
if (!atStake)
|
|
26881
27913
|
return silentDelete(m);
|
|
26882
27914
|
const thing = thingFor(files.map((f) => f.path), targets, repoRoot, env);
|
|
@@ -26889,9 +27921,38 @@ function deleteFilesFacts(m, env) {
|
|
|
26889
27921
|
...m.form === "find" ? { cannotTell: "which-files" } : {},
|
|
26890
27922
|
thing,
|
|
26891
27923
|
files: m.form === "find" ? null : n,
|
|
26892
|
-
neverSaved: m.form === "find" ? null : k
|
|
27924
|
+
neverSaved: m.form === "find" ? null : k,
|
|
27925
|
+
...base.pre.length === 0 ? {
|
|
27926
|
+
save: {
|
|
27927
|
+
repoRoot,
|
|
27928
|
+
paths: targets.map((t) => toSlash(relative5(repoRoot, t)) || "."),
|
|
27929
|
+
...keep === void 0 ? {} : { keep: (rel) => keep.deletes(rel) },
|
|
27930
|
+
lines: []
|
|
27931
|
+
}
|
|
27932
|
+
} : {}
|
|
26893
27933
|
};
|
|
26894
27934
|
}
|
|
27935
|
+
function closeFoldsAll(targets, env) {
|
|
27936
|
+
return targets.every((t) => {
|
|
27937
|
+
const root = recordRoot(t, env);
|
|
27938
|
+
if (root === null || !isInside(t, root))
|
|
27939
|
+
return false;
|
|
27940
|
+
const rel = toSlash(relative5(root, t));
|
|
27941
|
+
if (rel === "" || recordPatternFor(rel)?.deleteAsked !== false)
|
|
27942
|
+
return false;
|
|
27943
|
+
let at = root;
|
|
27944
|
+
for (const seg of rel.split("/")) {
|
|
27945
|
+
at = join12(at, seg);
|
|
27946
|
+
try {
|
|
27947
|
+
if (lstatSync2(at).isSymbolicLink())
|
|
27948
|
+
return false;
|
|
27949
|
+
} catch {
|
|
27950
|
+
return false;
|
|
27951
|
+
}
|
|
27952
|
+
}
|
|
27953
|
+
return true;
|
|
27954
|
+
});
|
|
27955
|
+
}
|
|
26895
27956
|
function silentDelete(m) {
|
|
26896
27957
|
return { type: m.type, match: m, atStake: false, lostForGood: [], recoverable: [], thing: null, files: 0, neverSaved: 0 };
|
|
26897
27958
|
}
|
|
@@ -26949,6 +28010,7 @@ function discardUnsavedFacts(m, env) {
|
|
|
26949
28010
|
const repoRoot = env.repoRoot(where.cwd);
|
|
26950
28011
|
const part = repoRoot === null ? null : owningPart(lost, repoRoot, env);
|
|
26951
28012
|
const thing = part !== null ? { kind: "part", name: part } : { kind: "paths", paths: lost };
|
|
28013
|
+
const saveable = repoRoot !== null && where.pre.length === 0 && m.form !== "stash-drop" && m.form !== "stash-clear" && m.form !== "worktree-remove";
|
|
26952
28014
|
return {
|
|
26953
28015
|
type: m.type,
|
|
26954
28016
|
match: m,
|
|
@@ -26956,7 +28018,8 @@ function discardUnsavedFacts(m, env) {
|
|
|
26956
28018
|
lostForGood: [{ kind: "unsaved-changes", count: lost.length }],
|
|
26957
28019
|
recoverable: [],
|
|
26958
28020
|
thing,
|
|
26959
|
-
files: lost.length
|
|
28021
|
+
files: lost.length,
|
|
28022
|
+
...saveable ? { save: { repoRoot, paths: lost, lines: [] } } : {}
|
|
26960
28023
|
};
|
|
26961
28024
|
}
|
|
26962
28025
|
function changedFiles(where, paths, which, env, skipUnmerged = false) {
|
|
@@ -27009,8 +28072,9 @@ function restoreTouchesIndex(m) {
|
|
|
27009
28072
|
}
|
|
27010
28073
|
function cleanFacts(m, env) {
|
|
27011
28074
|
const where = gitFolder(m, env);
|
|
27012
|
-
const
|
|
27013
|
-
|
|
28075
|
+
const cleaned = where === null ? null : cleanFiles(m, where, env);
|
|
28076
|
+
const lost = cleaned === null ? null : cleaned.lost;
|
|
28077
|
+
if (where === null || cleaned === null || lost === null) {
|
|
27014
28078
|
return { type: m.type, match: m, atStake: true, lostForGood: [], recoverable: [], cannotTell: "which-files", thing: null, files: null, neverSaved: null };
|
|
27015
28079
|
}
|
|
27016
28080
|
if (lost.length === 0)
|
|
@@ -27025,7 +28089,8 @@ function cleanFacts(m, env) {
|
|
|
27025
28089
|
recoverable: [],
|
|
27026
28090
|
thing: part !== null ? { kind: "part", name: part } : { kind: "paths", paths: lost },
|
|
27027
28091
|
files: lost.length,
|
|
27028
|
-
neverSaved: lost.length
|
|
28092
|
+
neverSaved: lost.length,
|
|
28093
|
+
...repoRoot !== null && where.pre.length === 0 ? { save: { repoRoot, paths: cleaned.removed, lines: [] } } : {}
|
|
27029
28094
|
};
|
|
27030
28095
|
}
|
|
27031
28096
|
function cleanFiles(m, where, env) {
|
|
@@ -27062,12 +28127,15 @@ function cleanFiles(m, where, env) {
|
|
|
27062
28127
|
return null;
|
|
27063
28128
|
const named = dry.value.split("\n").map((l) => /^Would remove (.*)$/.exec(l)?.[1]).filter((x) => x !== void 0 && x !== "");
|
|
27064
28129
|
if (named.length === 0)
|
|
27065
|
-
return [];
|
|
28130
|
+
return { lost: [], removed: [] };
|
|
27066
28131
|
const repoRoot = env.repoRoot(where.cwd);
|
|
27067
28132
|
if (repoRoot === null)
|
|
27068
28133
|
return null;
|
|
27069
|
-
const
|
|
27070
|
-
|
|
28134
|
+
const abs = named.map((n) => resolve4(where.cwd, n));
|
|
28135
|
+
const files = filesUnder(abs, repoRoot, env);
|
|
28136
|
+
if (files === null)
|
|
28137
|
+
return null;
|
|
28138
|
+
return { lost: files.filter((f) => f.neverSaved).map((f) => f.path), removed: abs.map((a) => toSlash(relative5(repoRoot, a)) || ".") };
|
|
27071
28139
|
}
|
|
27072
28140
|
function stashFiles(m, where, env) {
|
|
27073
28141
|
let refs;
|
|
@@ -27107,7 +28175,7 @@ function worktreeFiles(m, where, env) {
|
|
|
27107
28175
|
if (path === null)
|
|
27108
28176
|
return null;
|
|
27109
28177
|
const abs = realOrSelf(path);
|
|
27110
|
-
if (!
|
|
28178
|
+
if (!existsSync7(abs))
|
|
27111
28179
|
return [];
|
|
27112
28180
|
const top = env.repoRoot(abs);
|
|
27113
28181
|
if (top === null)
|
|
@@ -27119,19 +28187,23 @@ function worktreeFiles(m, where, env) {
|
|
|
27119
28187
|
}
|
|
27120
28188
|
function unmergedFacts(m, env) {
|
|
27121
28189
|
const where = gitFolder(m, env, ["clone"]);
|
|
27122
|
-
const
|
|
28190
|
+
const leaves = [];
|
|
27123
28191
|
if (where === null) {
|
|
27124
|
-
|
|
28192
|
+
leaves.push({ line: { branch: null, commits: null }, tip: null });
|
|
27125
28193
|
} else if (where.fresh === "clone") {
|
|
27126
|
-
|
|
28194
|
+
leaves.push({ line: { branch: null, commits: 0 }, tip: null });
|
|
27127
28195
|
} else if (m.form === "reset-to") {
|
|
27128
|
-
|
|
28196
|
+
leaves.push(resetLeaves(m.rev, where, env));
|
|
27129
28197
|
} else {
|
|
27130
28198
|
for (const b of m.branches)
|
|
27131
|
-
|
|
28199
|
+
leaves.push(branchLeaves(b, m.remoteTracking, where, env));
|
|
27132
28200
|
}
|
|
28201
|
+
const lines2 = leaves.map((l) => l.line);
|
|
27133
28202
|
const unknown2 = lines2.some((l) => l.commits === null);
|
|
27134
28203
|
const total = lines2.reduce((s, l) => s + (l.commits ?? 0), 0);
|
|
28204
|
+
const kept = leaves.filter((l) => (l.line.commits ?? 0) > 0);
|
|
28205
|
+
const repoRoot = where === null || where.fresh !== void 0 || where.pre.length > 0 ? null : env.repoRoot(where.cwd);
|
|
28206
|
+
const saveable = !unknown2 && repoRoot !== null && kept.every((l) => l.tip !== null);
|
|
27135
28207
|
return {
|
|
27136
28208
|
type: m.type,
|
|
27137
28209
|
match: m,
|
|
@@ -27139,44 +28211,46 @@ function unmergedFacts(m, env) {
|
|
|
27139
28211
|
lostForGood: [],
|
|
27140
28212
|
recoverable: total > 0 ? [{ kind: "local-changes", count: total }] : [],
|
|
27141
28213
|
...unknown2 ? { cannotTell: "which-line" } : {},
|
|
27142
|
-
lines: lines2
|
|
28214
|
+
lines: lines2,
|
|
28215
|
+
...saveable ? { save: { repoRoot, paths: [], lines: kept.map((l) => ({ name: m.remoteTracking ? null : l.line.branch, commit: l.tip })) } } : {}
|
|
27143
28216
|
};
|
|
27144
28217
|
}
|
|
27145
28218
|
function resetLeaves(rev, where, env) {
|
|
27146
28219
|
const current = currentBranch(where, env);
|
|
28220
|
+
const at = (commits, tip = null) => ({ line: { branch: current, commits }, tip });
|
|
27147
28221
|
if (rev === void 0)
|
|
27148
|
-
return
|
|
28222
|
+
return at(0);
|
|
27149
28223
|
let target;
|
|
27150
28224
|
if (rev.gitRev !== void 0) {
|
|
27151
28225
|
const r2 = env.git([...where.pre, ...rev.gitRev], where.cwd);
|
|
27152
28226
|
const id = r2.ok ? r2.value.trim() : "";
|
|
27153
28227
|
if (!/^[0-9a-f]{40,64}$/.test(id))
|
|
27154
|
-
return
|
|
28228
|
+
return at(null);
|
|
27155
28229
|
target = id;
|
|
27156
28230
|
} else {
|
|
27157
28231
|
if (!rev.literal || rev.text.startsWith("-"))
|
|
27158
|
-
return
|
|
28232
|
+
return at(null);
|
|
27159
28233
|
target = resolveCommit(rev.text, where, env);
|
|
27160
28234
|
}
|
|
27161
28235
|
if (target === null)
|
|
27162
|
-
return
|
|
28236
|
+
return at(0);
|
|
27163
28237
|
const head = resolveCommit("HEAD", where, env);
|
|
27164
28238
|
if (head === null)
|
|
27165
|
-
return
|
|
28239
|
+
return at(0);
|
|
27166
28240
|
const excludeCurrent = current === null ? [] : [`--exclude=${current}`];
|
|
27167
28241
|
const r = env.git([...where.pre, "rev-list", head, "--not", target, ...excludeCurrent, "--branches", "--remotes", "--tags"], where.cwd);
|
|
27168
|
-
return
|
|
28242
|
+
return at(r.ok ? lines(r.value).length : null, head);
|
|
27169
28243
|
}
|
|
27170
28244
|
function branchLeaves(b, remoteTracking, where, env) {
|
|
27171
28245
|
if (!b.literal || b.text.startsWith("-"))
|
|
27172
|
-
return { branch: b.literal ? b.text : null, commits: null };
|
|
28246
|
+
return { line: { branch: b.literal ? b.text : null, commits: null }, tip: null };
|
|
27173
28247
|
const ref = remoteTracking ? `refs/remotes/${b.text}` : `refs/heads/${b.text}`;
|
|
27174
28248
|
const tip = resolveCommit(ref, where, env);
|
|
27175
28249
|
if (tip === null)
|
|
27176
|
-
return { branch: b.text, commits: 0 };
|
|
28250
|
+
return { line: { branch: b.text, commits: 0 }, tip: null };
|
|
27177
28251
|
const exclude = remoteTracking ? ["--branches", `--exclude=${b.text}`, "--remotes", "--tags"] : [`--exclude=${b.text}`, "--branches", "--remotes", "--tags"];
|
|
27178
28252
|
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 };
|
|
28253
|
+
return { line: { branch: b.text, commits: r.ok ? lines(r.value).length : null }, tip };
|
|
27180
28254
|
}
|
|
27181
28255
|
function currentBranch(where, env) {
|
|
27182
28256
|
const r = env.git([...where.pre, "symbolic-ref", "--quiet", "--short", "HEAD"], where.cwd);
|
|
@@ -27451,7 +28525,7 @@ function publishFacts(m, env) {
|
|
|
27451
28525
|
const start = startFolder(m, env);
|
|
27452
28526
|
if (start !== null && PACKAGE_JSON_PROGRAMS.has(m.program)) {
|
|
27453
28527
|
try {
|
|
27454
|
-
const pkg = JSON.parse(
|
|
28528
|
+
const pkg = JSON.parse(readFileSync19(join12(start, "package.json"), "utf-8"));
|
|
27455
28529
|
if (typeof pkg["name"] === "string" && typeof pkg["version"] === "string" && pkg["name"] !== "" && pkg["version"] !== "") {
|
|
27456
28530
|
name = pkg["name"];
|
|
27457
28531
|
version2 = pkg["version"];
|
|
@@ -27499,9 +28573,9 @@ function shrinkRecordFacts(m, env) {
|
|
|
27499
28573
|
for (const file2 of [...found2.values()].sort((a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0)) {
|
|
27500
28574
|
let before;
|
|
27501
28575
|
try {
|
|
27502
|
-
if (!
|
|
28576
|
+
if (!lstatSync2(file2.abs).isFile())
|
|
27503
28577
|
continue;
|
|
27504
|
-
before =
|
|
28578
|
+
before = readFileSync19(file2.abs, "utf-8");
|
|
27505
28579
|
} catch {
|
|
27506
28580
|
continue;
|
|
27507
28581
|
}
|
|
@@ -27534,7 +28608,8 @@ function shrinkRecordFacts(m, env) {
|
|
|
27534
28608
|
lines: lines2,
|
|
27535
28609
|
removed,
|
|
27536
28610
|
history: h === null ? null : h.history,
|
|
27537
|
-
unsaved
|
|
28611
|
+
unsaved,
|
|
28612
|
+
...base.pre.length === 0 ? { save: { repoRoot: file2.root, paths: [file2.rel], lines: [] } } : {}
|
|
27538
28613
|
};
|
|
27539
28614
|
});
|
|
27540
28615
|
}
|
|
@@ -27552,7 +28627,7 @@ function recordRoot(abs, env) {
|
|
|
27552
28627
|
return null;
|
|
27553
28628
|
let dir = abs;
|
|
27554
28629
|
while (!isDirectory(dir)) {
|
|
27555
|
-
const up =
|
|
28630
|
+
const up = dirname6(dir);
|
|
27556
28631
|
if (up === dir)
|
|
27557
28632
|
return null;
|
|
27558
28633
|
dir = up;
|
|
@@ -27563,7 +28638,7 @@ function recordFileAt(abs, env) {
|
|
|
27563
28638
|
const root = recordRoot(abs, env);
|
|
27564
28639
|
if (root === null || !isInside(abs, root))
|
|
27565
28640
|
return null;
|
|
27566
|
-
const rel = toSlash(
|
|
28641
|
+
const rel = toSlash(relative5(root, abs));
|
|
27567
28642
|
if (rel === "")
|
|
27568
28643
|
return null;
|
|
27569
28644
|
const row = recordPatternFor(rel);
|
|
@@ -27573,11 +28648,11 @@ function recordFilesUnder(folder, env) {
|
|
|
27573
28648
|
const root = recordRoot(folder, env);
|
|
27574
28649
|
if (root === null)
|
|
27575
28650
|
return [];
|
|
27576
|
-
const relFolder = toSlash(
|
|
28651
|
+
const relFolder = toSlash(relative5(root, folder));
|
|
27577
28652
|
const starts = /* @__PURE__ */ new Set();
|
|
27578
28653
|
for (const { pattern } of PROJECT_RECORD_PATTERNS) {
|
|
27579
28654
|
if (relFolder === "" || patternOwns(relFolder, pattern))
|
|
27580
|
-
starts.add(
|
|
28655
|
+
starts.add(join12(root, pattern));
|
|
27581
28656
|
else if (patternOwns(pattern, relFolder))
|
|
27582
28657
|
starts.add(folder);
|
|
27583
28658
|
}
|
|
@@ -27591,7 +28666,7 @@ function recordFilesUnder(folder, env) {
|
|
|
27591
28666
|
seen.add(p);
|
|
27592
28667
|
let st;
|
|
27593
28668
|
try {
|
|
27594
|
-
st =
|
|
28669
|
+
st = lstatSync2(p);
|
|
27595
28670
|
} catch {
|
|
27596
28671
|
continue;
|
|
27597
28672
|
}
|
|
@@ -27603,7 +28678,7 @@ function recordFilesUnder(folder, env) {
|
|
|
27603
28678
|
continue;
|
|
27604
28679
|
}
|
|
27605
28680
|
for (const n of names)
|
|
27606
|
-
stack.push(
|
|
28681
|
+
stack.push(join12(p, n));
|
|
27607
28682
|
} else if (st.isFile()) {
|
|
27608
28683
|
const r = recordFileAt(p, env);
|
|
27609
28684
|
if (r !== null)
|
|
@@ -27616,7 +28691,7 @@ function recordHistory(files, env) {
|
|
|
27616
28691
|
const out = /* @__PURE__ */ new Map();
|
|
27617
28692
|
const byTree = /* @__PURE__ */ new Map();
|
|
27618
28693
|
for (const abs of files) {
|
|
27619
|
-
const top = env.repoRoot(
|
|
28694
|
+
const top = env.repoRoot(dirname6(abs));
|
|
27620
28695
|
if (top === null || !isInside(abs, top))
|
|
27621
28696
|
continue;
|
|
27622
28697
|
const list = byTree.get(top) ?? [];
|
|
@@ -27624,7 +28699,7 @@ function recordHistory(files, env) {
|
|
|
27624
28699
|
byTree.set(top, list);
|
|
27625
28700
|
}
|
|
27626
28701
|
for (const [top, list] of byTree) {
|
|
27627
|
-
const specs = list.map((abs) => toSlash(
|
|
28702
|
+
const specs = list.map((abs) => toSlash(relative5(top, abs)));
|
|
27628
28703
|
const tracked = trackedOf(specs, top, env);
|
|
27629
28704
|
if (tracked === null)
|
|
27630
28705
|
continue;
|
|
@@ -27705,14 +28780,7 @@ function oneLine(text) {
|
|
|
27705
28780
|
return text.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "").replace(/\s+/g, " ").trim();
|
|
27706
28781
|
}
|
|
27707
28782
|
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
|
-
});
|
|
28783
|
+
const staked = orderedStaked(facts);
|
|
27716
28784
|
const reason = reasonSentence(description);
|
|
27717
28785
|
if (staked.length === 0)
|
|
27718
28786
|
return couldNotTellQuestion(description);
|
|
@@ -27721,15 +28789,45 @@ function questionFor(facts, description) {
|
|
|
27721
28789
|
return `Halfcycle: ${whatOf(only)} ${reason} If you allow it, ${lostOf(only)} ${NOTHING_LOST_ON_NO}`;
|
|
27722
28790
|
}
|
|
27723
28791
|
const shown2 = staked.slice(0, MAX_LISTED).map((f) => `${whatOf(f)} If you allow it, ${lostOf(f)}`);
|
|
27724
|
-
|
|
27725
|
-
|
|
27726
|
-
|
|
28792
|
+
return `Halfcycle: ${shown2.join(" ")}${moreLine(staked.length - shown2.length)} ${reason} ${NOTHING_LOST_ON_NO}`;
|
|
28793
|
+
}
|
|
28794
|
+
function orderedStaked(facts) {
|
|
28795
|
+
const said = /* @__PURE__ */ new Set();
|
|
28796
|
+
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) => {
|
|
28797
|
+
const part = `${whatOf(f)} ${lostOf(f)}`;
|
|
28798
|
+
if (said.has(part))
|
|
28799
|
+
return false;
|
|
28800
|
+
said.add(part);
|
|
28801
|
+
return true;
|
|
28802
|
+
});
|
|
28803
|
+
}
|
|
28804
|
+
function moreLine(more) {
|
|
28805
|
+
return more > 0 ? ` And ${more} more risky ${more === 1 ? "step" : "steps"}.` : "";
|
|
28806
|
+
}
|
|
28807
|
+
var UNNAMED_PUSH_WHAT = "Replace a line of work on the shared copy.";
|
|
28808
|
+
function whatsText(facts, extraPush = false) {
|
|
28809
|
+
const staked = orderedStaked(facts);
|
|
28810
|
+
const items = staked.map((f) => ({ order: riskyTypeOrder(f.type), what: whatOf(f) }));
|
|
28811
|
+
const pushOrder = riskyTypeOrder("rewrite-shared-history");
|
|
28812
|
+
if (extraPush && !staked.some((f) => f.type === "rewrite-shared-history")) {
|
|
28813
|
+
const at = items.findIndex((x) => x.order > pushOrder);
|
|
28814
|
+
items.splice(at < 0 ? items.length : at, 0, { order: pushOrder, what: UNNAMED_PUSH_WHAT });
|
|
28815
|
+
}
|
|
28816
|
+
const shown2 = items.slice(0, MAX_LISTED).map((x) => x.what);
|
|
28817
|
+
return `${shown2.join(" ")}${moreLine(items.length - shown2.length)}`;
|
|
28818
|
+
}
|
|
28819
|
+
function typeSentenceFor(facts, extraPush = false) {
|
|
28820
|
+
const whats = whatsText(facts, extraPush);
|
|
28821
|
+
return whats === "" ? AUTONOMY_COULD_NOT_TELL_SENTENCE : `Halfcycle: ${whats}`;
|
|
28822
|
+
}
|
|
28823
|
+
function savedWhatFor(facts) {
|
|
28824
|
+
return whatsText(facts);
|
|
27727
28825
|
}
|
|
27728
28826
|
function refusalText(question) {
|
|
27729
28827
|
return `${question} ${TURN_QUESTIONS_ON_LINE}`;
|
|
27730
28828
|
}
|
|
27731
28829
|
function couldNotTellQuestion(description) {
|
|
27732
|
-
return
|
|
28830
|
+
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
28831
|
}
|
|
27734
28832
|
function whatOf(f) {
|
|
27735
28833
|
switch (f.type) {
|
|
@@ -27811,7 +28909,7 @@ function pushWhat(f) {
|
|
|
27811
28909
|
const verb = lines2.length > 0 && lines2.every((l) => l.deletes) ? "Delete" : "Replace";
|
|
27812
28910
|
const named = lines2.filter((l) => l.branch !== null).map((l) => l.branch);
|
|
27813
28911
|
if (named.length === 0)
|
|
27814
|
-
return `${verb} a line of work on the shared copy.`;
|
|
28912
|
+
return verb === "Replace" ? UNNAMED_PUSH_WHAT : `${verb} a line of work on the shared copy.`;
|
|
27815
28913
|
return `${verb} the shared copy's ${named.length === 1 ? "line" : "lines"} of work ${listNames(named)}.`;
|
|
27816
28914
|
}
|
|
27817
28915
|
function pushLost(f) {
|
|
@@ -28013,16 +29111,71 @@ function answerModeFor(permissionMode) {
|
|
|
28013
29111
|
return typeof permissionMode === "string" && QUESTIONS_ON_MODES.has(permissionMode) ? "questions-on" : "questions-off";
|
|
28014
29112
|
}
|
|
28015
29113
|
var REFUSED_IN_EVERY_MODE = /* @__PURE__ */ new Set(["outside-own-copy"]);
|
|
28016
|
-
function
|
|
29114
|
+
function autonomousRowOf(type) {
|
|
29115
|
+
switch (type) {
|
|
29116
|
+
case "delete-files":
|
|
29117
|
+
case "discard-unsaved":
|
|
29118
|
+
case "delete-unmerged-work":
|
|
29119
|
+
case "shrink-record":
|
|
29120
|
+
return "save";
|
|
29121
|
+
case "rewrite-shared-history":
|
|
29122
|
+
case "wipe-data":
|
|
29123
|
+
return "wait-item-1";
|
|
29124
|
+
case "publish":
|
|
29125
|
+
case "deploy":
|
|
29126
|
+
return "publish";
|
|
29127
|
+
case "read-credentials":
|
|
29128
|
+
return "never";
|
|
29129
|
+
case "outside-own-copy":
|
|
29130
|
+
return "every-mode";
|
|
29131
|
+
default: {
|
|
29132
|
+
const unhandled = type;
|
|
29133
|
+
return unhandled;
|
|
29134
|
+
}
|
|
29135
|
+
}
|
|
29136
|
+
}
|
|
29137
|
+
function savedByAutonomy(type) {
|
|
29138
|
+
return autonomousRowOf(type) === "save";
|
|
29139
|
+
}
|
|
29140
|
+
function ownerWaitItemNumber(id) {
|
|
29141
|
+
return OWNER_WAIT_ITEM_IDS.indexOf(id) + 1;
|
|
29142
|
+
}
|
|
29143
|
+
function decide(action, facts, mode, mayPublishNow = () => false) {
|
|
28017
29144
|
void action;
|
|
28018
29145
|
if (facts.some((f) => REFUSED_IN_EVERY_MODE.has(f.type)))
|
|
28019
29146
|
return "refuse";
|
|
28020
|
-
|
|
29147
|
+
switch (mode) {
|
|
29148
|
+
case "questions-on":
|
|
29149
|
+
return "ask";
|
|
29150
|
+
case "questions-off":
|
|
29151
|
+
return "refuse";
|
|
29152
|
+
case "autonomous":
|
|
29153
|
+
return decideAutonomous(facts, mayPublishNow);
|
|
29154
|
+
default: {
|
|
29155
|
+
const unhandled = mode;
|
|
29156
|
+
void unhandled;
|
|
29157
|
+
return "refuse";
|
|
29158
|
+
}
|
|
29159
|
+
}
|
|
29160
|
+
}
|
|
29161
|
+
function decideAutonomous(facts, mayPublishNow) {
|
|
29162
|
+
if (facts.length === 0)
|
|
29163
|
+
return { autonomous: "could-not-tell" };
|
|
29164
|
+
const rows = [...facts].sort((a, b) => riskyTypeOrder(a.type) - riskyTypeOrder(b.type)).map((f) => autonomousRowOf(f.type));
|
|
29165
|
+
if (rows.includes("never"))
|
|
29166
|
+
return { autonomous: "never-under-autonomy" };
|
|
29167
|
+
const firstWait = rows.find((r) => r === "wait-item-1" || r === "publish");
|
|
29168
|
+
if (firstWait === "wait-item-1")
|
|
29169
|
+
return { autonomous: "waits-for-owner", item: ownerWaitItemNumber("delete-users-data") };
|
|
29170
|
+
if (firstWait === "publish" && !mayPublishNow()) {
|
|
29171
|
+
return { autonomous: "waits-for-owner", item: ownerWaitItemNumber("publish-or-release") };
|
|
29172
|
+
}
|
|
29173
|
+
return rows.includes("save") ? { autonomous: "save-then-let-through" } : { autonomous: "let-through" };
|
|
28021
29174
|
}
|
|
28022
29175
|
|
|
28023
29176
|
// dist/risky/own-copy.js
|
|
28024
|
-
import { readFileSync as
|
|
28025
|
-
import { join as
|
|
29177
|
+
import { readFileSync as readFileSync20 } from "node:fs";
|
|
29178
|
+
import { join as join13, posix as posix2, win32 as win322 } from "node:path";
|
|
28026
29179
|
var OWN_COPY_START_TOOL = "Agent";
|
|
28027
29180
|
var OWN_COPY_WRITE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
|
|
28028
29181
|
var OWN_COPY_ISOLATION = "worktree";
|
|
@@ -28043,7 +29196,7 @@ function stringField2(o, key) {
|
|
|
28043
29196
|
}
|
|
28044
29197
|
function readOr(deps, path) {
|
|
28045
29198
|
try {
|
|
28046
|
-
return (deps.readFile ?? ((p) =>
|
|
29199
|
+
return (deps.readFile ?? ((p) => readFileSync20(p, "utf-8")))(path);
|
|
28047
29200
|
} catch {
|
|
28048
29201
|
return null;
|
|
28049
29202
|
}
|
|
@@ -28083,7 +29236,7 @@ function typeCanChangeFiles(subagentType, deps) {
|
|
|
28083
29236
|
return true;
|
|
28084
29237
|
if (AGENT_FILE_NAME.test(type)) {
|
|
28085
29238
|
for (const base of [deps.projectDir, deps.home]) {
|
|
28086
|
-
const text = readOr(deps,
|
|
29239
|
+
const text = readOr(deps, join13(base, ".claude", "agents", `${type}.md`));
|
|
28087
29240
|
if (text === null)
|
|
28088
29241
|
continue;
|
|
28089
29242
|
const tools = frontmatterTools(text);
|
|
@@ -28107,94 +29260,597 @@ function partOf(top, rel, p, deps) {
|
|
|
28107
29260
|
const owner = ownerOf(rel, checked.description);
|
|
28108
29261
|
return checked.description.parts.find((p2) => p2.id === owner)?.name ?? null;
|
|
28109
29262
|
} catch {
|
|
28110
|
-
return null;
|
|
29263
|
+
return null;
|
|
29264
|
+
}
|
|
29265
|
+
}
|
|
29266
|
+
function isMainCopy(c) {
|
|
29267
|
+
return c.top === c.main;
|
|
29268
|
+
}
|
|
29269
|
+
function judgeOwnCopy(input, call, deps) {
|
|
29270
|
+
try {
|
|
29271
|
+
if (call.toolName === OWN_COPY_START_TOOL)
|
|
29272
|
+
return judgeStart(input, call, deps);
|
|
29273
|
+
if (OWN_COPY_WRITE_TOOLS.has(call.toolName))
|
|
29274
|
+
return judgeChange(input, call, deps);
|
|
29275
|
+
return PASS;
|
|
29276
|
+
} catch {
|
|
29277
|
+
return PASS;
|
|
29278
|
+
}
|
|
29279
|
+
}
|
|
29280
|
+
function judgeStart(input, call, deps) {
|
|
29281
|
+
if (call.toolInput["isolation"] === OWN_COPY_ISOLATION)
|
|
29282
|
+
return PASS;
|
|
29283
|
+
if (!typeCanChangeFiles(call.toolInput["subagent_type"], deps))
|
|
29284
|
+
return PASS;
|
|
29285
|
+
const cwd = stringField2(input, "cwd");
|
|
29286
|
+
if (cwd === null || whichCopy(cwd, deps.walk) === null)
|
|
29287
|
+
return UNKNOWN;
|
|
29288
|
+
const record2 = readLiveness(stringField2(input, "session_id"), cwd, deps.liveness);
|
|
29289
|
+
if (!runsBesideOtherWork(call.toolInput, withoutAgent(record2, stringField2(input, "agent_id"))))
|
|
29290
|
+
return PASS;
|
|
29291
|
+
return { kind: "refused", facts: { type: "outside-own-copy", atStake: true, arm: "start" } };
|
|
29292
|
+
}
|
|
29293
|
+
function judgeChange(input, call, deps) {
|
|
29294
|
+
const cwd = stringField2(input, "cwd");
|
|
29295
|
+
const own = cwd === null ? null : whichCopy(cwd, deps.walk);
|
|
29296
|
+
if (cwd === null || own === null)
|
|
29297
|
+
return UNKNOWN;
|
|
29298
|
+
const given = targetOf(call);
|
|
29299
|
+
if (given === null)
|
|
29300
|
+
return PASS;
|
|
29301
|
+
const rules = deps.walk?.rules ?? machineRules();
|
|
29302
|
+
const p = rules === "windows" ? win322 : posix2;
|
|
29303
|
+
const place = copyHolding(given, deps.walk);
|
|
29304
|
+
if (place === null || place.copy.common !== own.common)
|
|
29305
|
+
return PASS;
|
|
29306
|
+
const target = place.copy;
|
|
29307
|
+
const agentId = stringField2(input, "agent_id");
|
|
29308
|
+
let arm = null;
|
|
29309
|
+
let record2;
|
|
29310
|
+
const live = () => record2 === void 0 ? record2 = readLiveness(stringField2(input, "session_id"), cwd, deps.liveness) : record2;
|
|
29311
|
+
if (agentId !== null) {
|
|
29312
|
+
if (target.top !== own.top)
|
|
29313
|
+
arm = "other-copy";
|
|
29314
|
+
else {
|
|
29315
|
+
const rec = live();
|
|
29316
|
+
const mainTop = rec?.main?.top ?? (isMainCopy(own) ? own.top : null);
|
|
29317
|
+
const others = withoutAgent(rec, agentId);
|
|
29318
|
+
if (mainTop === own.top && others !== null && others.agents.length > 0)
|
|
29319
|
+
arm = "main-copy-while-others";
|
|
29320
|
+
}
|
|
29321
|
+
} else if (target.top !== own.top && (live()?.agents ?? []).some((a) => a.top === target.top)) {
|
|
29322
|
+
arm = "agents-copy";
|
|
29323
|
+
}
|
|
29324
|
+
if (arm === null)
|
|
29325
|
+
return PASS;
|
|
29326
|
+
const inFolder = place.folder.length > target.top.length ? place.folder.slice(target.top.length + 1) : "";
|
|
29327
|
+
const rel = [inFolder, place.rest].filter((s) => s !== "").join(p.sep).split(p.sep).join("/");
|
|
29328
|
+
return {
|
|
29329
|
+
kind: "refused",
|
|
29330
|
+
facts: {
|
|
29331
|
+
type: "outside-own-copy",
|
|
29332
|
+
atStake: true,
|
|
29333
|
+
arm,
|
|
29334
|
+
part: partOf(target.top, rel, p, deps),
|
|
29335
|
+
file: rel,
|
|
29336
|
+
ownPath: p.join(own.top, ...rel.split("/")),
|
|
29337
|
+
ownIsMain: isMainCopy(own),
|
|
29338
|
+
targetIsMain: isMainCopy(target)
|
|
29339
|
+
}
|
|
29340
|
+
};
|
|
29341
|
+
}
|
|
29342
|
+
|
|
29343
|
+
// dist/risky/save.js
|
|
29344
|
+
import { randomBytes } from "node:crypto";
|
|
29345
|
+
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";
|
|
29346
|
+
import { homedir as homedir8 } from "node:os";
|
|
29347
|
+
import { dirname as dirname7, isAbsolute as isAbsolute3, join as join14, resolve as resolve5 } from "node:path";
|
|
29348
|
+
var SAVE_LIMIT_BYTES = 2e8;
|
|
29349
|
+
var SAVE_BUDGET_MS = 5e3;
|
|
29350
|
+
function notSaved(reason) {
|
|
29351
|
+
return { saved: false, reason };
|
|
29352
|
+
}
|
|
29353
|
+
function savedFolderOf(engagementId, home = homedir8()) {
|
|
29354
|
+
if (!isEngagementId(engagementId))
|
|
29355
|
+
return null;
|
|
29356
|
+
return join14(home, HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, SAVED_DIR_NAME);
|
|
29357
|
+
}
|
|
29358
|
+
function saveFirst(staked, ctx) {
|
|
29359
|
+
const now = ctx.now ?? Date.now;
|
|
29360
|
+
const deadline = now() + (ctx.budgetMs ?? SAVE_BUDGET_MS);
|
|
29361
|
+
const folder = savedFolderOf(ctx.engagementId, ctx.home);
|
|
29362
|
+
if (folder === null)
|
|
29363
|
+
return notSaved(AUTONOMY_SAVE_FAILED_REASON);
|
|
29364
|
+
const plan = planOf(staked);
|
|
29365
|
+
if (!plan.ok)
|
|
29366
|
+
return notSaved(plan.reason);
|
|
29367
|
+
const g = new SaveGit(plan.root, deadline, now);
|
|
29368
|
+
const top = g.run(["rev-parse", "--show-toplevel"]);
|
|
29369
|
+
if (!top.ok || realOrSelf2(top.value.trim()) !== plan.root)
|
|
29370
|
+
return notSaved(AUTONOMY_SAVE_NOT_COVERED_REASON);
|
|
29371
|
+
const head = g.run(["rev-parse", "--verify", "--quiet", "HEAD^{commit}"]);
|
|
29372
|
+
const headCommit2 = head.ok ? head.value.trim() : "";
|
|
29373
|
+
if (!/^[0-9a-f]{40,64}$/.test(headCommit2))
|
|
29374
|
+
return notSaved(AUTONOMY_SAVE_NO_HISTORY_REASON);
|
|
29375
|
+
try {
|
|
29376
|
+
pruneSaves(plan.root, ctx.engagementId, { ...ctx.home === void 0 ? {} : { home: ctx.home }, now, deadline });
|
|
29377
|
+
} catch {
|
|
29378
|
+
}
|
|
29379
|
+
const found2 = filesOf(plan, g, deadline);
|
|
29380
|
+
if (!found2.ok)
|
|
29381
|
+
return notSaved(found2.reason);
|
|
29382
|
+
let saveDir = null;
|
|
29383
|
+
try {
|
|
29384
|
+
const id = freshId(folder, ctx.newId);
|
|
29385
|
+
if (id === null)
|
|
29386
|
+
return notSaved(AUTONOMY_SAVE_FAILED_REASON);
|
|
29387
|
+
saveDir = join14(folder, id);
|
|
29388
|
+
const repository = repositoryOf(g);
|
|
29389
|
+
if (repository === null)
|
|
29390
|
+
return failed(saveDir);
|
|
29391
|
+
writeFileSync12(join14(saveDir, SAVED_REPOSITORY_FILE_NAME), `${repository}
|
|
29392
|
+
`, { mode: 384 });
|
|
29393
|
+
const privateIndex = join14(saveDir, PRIVATE_INDEX_NAME);
|
|
29394
|
+
const commit = saveCommit(id, headCommit2, found2.tracked, plan.lines, privateIndex, g, now());
|
|
29395
|
+
rmSync2(privateIndex, { force: true });
|
|
29396
|
+
if (commit === null)
|
|
29397
|
+
return failed(saveDir);
|
|
29398
|
+
const copiesDir = join14(saveDir, SAVED_COPIES_DIR_NAME);
|
|
29399
|
+
for (const rel of found2.copies) {
|
|
29400
|
+
if (now() > deadline || !copyOne(join14(plan.root, rel), join14(copiesDir, rel)))
|
|
29401
|
+
return failed(saveDir);
|
|
29402
|
+
}
|
|
29403
|
+
const description = savedSchema.safeParse({
|
|
29404
|
+
id,
|
|
29405
|
+
savedAt: new Date(now()).toISOString(),
|
|
29406
|
+
ref: savedRefOf(id),
|
|
29407
|
+
what: ctx.what.slice(0, 2e3),
|
|
29408
|
+
paths: [
|
|
29409
|
+
...found2.tracked.map((path) => ({ path, keptIn: "commit" })),
|
|
29410
|
+
...found2.copies.map((path) => ({ path, keptIn: "copy" }))
|
|
29411
|
+
],
|
|
29412
|
+
branches: plan.lines.filter((l) => l.name !== null).map((l) => ({ name: l.name, commit: l.commit }))
|
|
29413
|
+
});
|
|
29414
|
+
if (!description.success)
|
|
29415
|
+
return failed(saveDir);
|
|
29416
|
+
const file2 = join14(saveDir, SAVED_FILE_NAME);
|
|
29417
|
+
writeFileSync12(`${file2}.part`, `${JSON.stringify(description.data, null, 2)}
|
|
29418
|
+
`, { mode: 384 });
|
|
29419
|
+
renameSync10(`${file2}.part`, file2);
|
|
29420
|
+
ctx.beforeRef?.();
|
|
29421
|
+
const ref = g.run(["update-ref", "--no-deref", savedRefOf(id), commit, ""]);
|
|
29422
|
+
if (!ref.ok)
|
|
29423
|
+
return failed(saveDir);
|
|
29424
|
+
return { saved: true, id, description: description.data };
|
|
29425
|
+
} catch {
|
|
29426
|
+
return saveDir === null ? notSaved(AUTONOMY_SAVE_FAILED_REASON) : failed(saveDir);
|
|
29427
|
+
}
|
|
29428
|
+
}
|
|
29429
|
+
var PRIVATE_INDEX_NAME = "index.part";
|
|
29430
|
+
function failed(saveDir) {
|
|
29431
|
+
try {
|
|
29432
|
+
rmSync2(saveDir, { recursive: true, force: true });
|
|
29433
|
+
} catch {
|
|
29434
|
+
}
|
|
29435
|
+
return notSaved(AUTONOMY_SAVE_FAILED_REASON);
|
|
29436
|
+
}
|
|
29437
|
+
function planOf(staked) {
|
|
29438
|
+
if (staked.length === 0)
|
|
29439
|
+
return { ok: false, reason: AUTONOMY_COULD_NOT_TELL_REASON };
|
|
29440
|
+
let root = null;
|
|
29441
|
+
const specs = [];
|
|
29442
|
+
const lines2 = /* @__PURE__ */ new Map();
|
|
29443
|
+
for (const f of staked) {
|
|
29444
|
+
const t = f.save;
|
|
29445
|
+
if (t === void 0)
|
|
29446
|
+
return { ok: false, reason: f.cannotTell === void 0 ? AUTONOMY_SAVE_NOT_COVERED_REASON : AUTONOMY_COULD_NOT_TELL_REASON };
|
|
29447
|
+
const r = realOrSelf2(resolve5(t.repoRoot));
|
|
29448
|
+
if (root === null)
|
|
29449
|
+
root = r;
|
|
29450
|
+
else if (root !== r)
|
|
29451
|
+
return { ok: false, reason: AUTONOMY_SAVE_NOT_COVERED_REASON };
|
|
29452
|
+
for (const p of t.paths) {
|
|
29453
|
+
if (!isInsideRel(p))
|
|
29454
|
+
return { ok: false, reason: AUTONOMY_SAVE_NOT_COVERED_REASON };
|
|
29455
|
+
specs.push(t.keep === void 0 ? { path: p } : { path: p, keep: t.keep });
|
|
29456
|
+
}
|
|
29457
|
+
for (const l of t.lines)
|
|
29458
|
+
if (!lines2.has(l.commit) || l.name !== null)
|
|
29459
|
+
lines2.set(l.commit, l);
|
|
29460
|
+
}
|
|
29461
|
+
return { ok: true, root, specs, lines: [...lines2.values()] };
|
|
29462
|
+
}
|
|
29463
|
+
function isInsideRel(p) {
|
|
29464
|
+
if (p === ".")
|
|
29465
|
+
return true;
|
|
29466
|
+
if (p === "" || isAbsolute3(p) || p.includes("\\"))
|
|
29467
|
+
return false;
|
|
29468
|
+
return p.split("/").every((s) => s !== "" && s !== "." && s !== "..");
|
|
29469
|
+
}
|
|
29470
|
+
function under(rel, spec) {
|
|
29471
|
+
return spec === "." || rel === spec || rel.startsWith(`${spec}/`);
|
|
29472
|
+
}
|
|
29473
|
+
function reached(rel, specs) {
|
|
29474
|
+
return specs.some((s) => under(rel, s.path) && (s.keep === void 0 || s.keep(rel)));
|
|
29475
|
+
}
|
|
29476
|
+
function filesOf(plan, g, deadline) {
|
|
29477
|
+
if (plan.specs.length === 0)
|
|
29478
|
+
return { ok: true, tracked: [], copies: [] };
|
|
29479
|
+
const specs = [...new Set(plan.specs.map((s) => s.path))];
|
|
29480
|
+
const listed = g.run(["--literal-pathspecs", "ls-files", "-z", "--full-name", "--stage", "--", ...specs]);
|
|
29481
|
+
if (!listed.ok)
|
|
29482
|
+
return { ok: false, reason: AUTONOMY_SAVE_FAILED_REASON };
|
|
29483
|
+
const trackedAll = /* @__PURE__ */ new Set();
|
|
29484
|
+
const tracked = /* @__PURE__ */ new Set();
|
|
29485
|
+
for (const rec of listed.value.split("\0")) {
|
|
29486
|
+
const tab = rec.indexOf(" ");
|
|
29487
|
+
if (tab < 0)
|
|
29488
|
+
continue;
|
|
29489
|
+
const mode = rec.slice(0, rec.indexOf(" "));
|
|
29490
|
+
const path = rec.slice(tab + 1);
|
|
29491
|
+
if (mode === "160000")
|
|
29492
|
+
continue;
|
|
29493
|
+
trackedAll.add(path);
|
|
29494
|
+
if (!reached(path, plan.specs))
|
|
29495
|
+
continue;
|
|
29496
|
+
const st = lstatOrNull(join14(plan.root, path));
|
|
29497
|
+
if (st !== null && (st.isFile() || st.isSymbolicLink()))
|
|
29498
|
+
tracked.add(path);
|
|
29499
|
+
}
|
|
29500
|
+
const copies = /* @__PURE__ */ new Set();
|
|
29501
|
+
let bytes = 0;
|
|
29502
|
+
let seen = 0;
|
|
29503
|
+
const stack = [...specs];
|
|
29504
|
+
while (stack.length > 0) {
|
|
29505
|
+
const rel = stack.pop();
|
|
29506
|
+
if (rel === ".git")
|
|
29507
|
+
continue;
|
|
29508
|
+
const abs = rel === "." ? plan.root : join14(plan.root, rel);
|
|
29509
|
+
const st = lstatOrNull(abs);
|
|
29510
|
+
if (st === null)
|
|
29511
|
+
continue;
|
|
29512
|
+
if (st.isDirectory()) {
|
|
29513
|
+
let names;
|
|
29514
|
+
try {
|
|
29515
|
+
names = readdirSync9(abs);
|
|
29516
|
+
} catch {
|
|
29517
|
+
return { ok: false, reason: AUTONOMY_SAVE_FAILED_REASON };
|
|
29518
|
+
}
|
|
29519
|
+
for (const n of names) {
|
|
29520
|
+
const child = rel === "." ? n : `${rel}/${n}`;
|
|
29521
|
+
if (child !== ".git")
|
|
29522
|
+
stack.push(child);
|
|
29523
|
+
}
|
|
29524
|
+
} else if ((st.isFile() || st.isSymbolicLink()) && !trackedAll.has(rel) && !copies.has(rel) && reached(rel, plan.specs)) {
|
|
29525
|
+
copies.add(rel);
|
|
29526
|
+
bytes += st.size;
|
|
29527
|
+
if (bytes > SAVE_LIMIT_BYTES)
|
|
29528
|
+
return { ok: false, reason: AUTONOMY_SAVE_TOO_LARGE_REASON };
|
|
29529
|
+
}
|
|
29530
|
+
if (++seen % 1024 === 0 && Date.now() > deadline)
|
|
29531
|
+
return { ok: false, reason: AUTONOMY_SAVE_FAILED_REASON };
|
|
29532
|
+
}
|
|
29533
|
+
return { ok: true, tracked: [...tracked].sort(), copies: [...copies].sort() };
|
|
29534
|
+
}
|
|
29535
|
+
function copyOne(src, dst) {
|
|
29536
|
+
const st = lstatOrNull(src);
|
|
29537
|
+
if (st === null)
|
|
29538
|
+
return false;
|
|
29539
|
+
mkdirSync8(dirname7(dst), { recursive: true, mode: 448 });
|
|
29540
|
+
if (st.isSymbolicLink()) {
|
|
29541
|
+
symlinkSync(readlinkSync(src), dst);
|
|
29542
|
+
return true;
|
|
29543
|
+
}
|
|
29544
|
+
copyFileSync(src, dst, fsConstants.COPYFILE_EXCL | fsConstants.COPYFILE_FICLONE);
|
|
29545
|
+
chmodSync2(dst, st.mode & 4095);
|
|
29546
|
+
return statSync8(dst).size === st.size;
|
|
29547
|
+
}
|
|
29548
|
+
function saveCommit(id, head, tracked, lines2, privateIndex, g, at) {
|
|
29549
|
+
const indexPath = g.run(["rev-parse", "--git-path", "index"]);
|
|
29550
|
+
if (!indexPath.ok)
|
|
29551
|
+
return null;
|
|
29552
|
+
const projectIndex = resolve5(g.root, indexPath.value.trim());
|
|
29553
|
+
const env = commitEnv(privateIndex, at);
|
|
29554
|
+
if (lstatOrNull(projectIndex) !== null)
|
|
29555
|
+
copyFileSync(projectIndex, privateIndex);
|
|
29556
|
+
else if (!g.run(["read-tree", head], env).ok)
|
|
29557
|
+
return null;
|
|
29558
|
+
const stagedTree = g.run(["write-tree"], env);
|
|
29559
|
+
if (!stagedTree.ok)
|
|
29560
|
+
return null;
|
|
29561
|
+
const staged = g.run(["commit-tree", "--no-gpg-sign", stagedTree.value.trim(), "-p", head, "-m", `Halfcycle save ${id}: staged changes`], env);
|
|
29562
|
+
if (!staged.ok)
|
|
29563
|
+
return null;
|
|
29564
|
+
if (tracked.length > 0 && !g.run(["update-index", "-z", "--stdin"], env, `${tracked.join("\0")}\0`).ok)
|
|
29565
|
+
return null;
|
|
29566
|
+
const tree = g.run(["write-tree"], env);
|
|
29567
|
+
if (!tree.ok)
|
|
29568
|
+
return null;
|
|
29569
|
+
const parents = [.../* @__PURE__ */ new Set([head, staged.value.trim(), ...lines2.map((l) => l.commit)])];
|
|
29570
|
+
const commit = g.run(["commit-tree", "--no-gpg-sign", tree.value.trim(), ...parents.flatMap((p) => ["-p", p]), "-m", `Halfcycle save ${id}`], env);
|
|
29571
|
+
if (!commit.ok)
|
|
29572
|
+
return null;
|
|
29573
|
+
const sha = commit.value.trim();
|
|
29574
|
+
return /^[0-9a-f]{40,64}$/.test(sha) ? sha : null;
|
|
29575
|
+
}
|
|
29576
|
+
function commitEnv(privateIndex, at) {
|
|
29577
|
+
const date5 = `${String(Math.floor(at / 1e3))} +0000`;
|
|
29578
|
+
return {
|
|
29579
|
+
GIT_INDEX_FILE: privateIndex,
|
|
29580
|
+
GIT_OPTIONAL_LOCKS: "0",
|
|
29581
|
+
GIT_AUTHOR_NAME: "Halfcycle",
|
|
29582
|
+
GIT_AUTHOR_EMAIL: "save@halfcycle.invalid",
|
|
29583
|
+
GIT_AUTHOR_DATE: date5,
|
|
29584
|
+
GIT_COMMITTER_NAME: "Halfcycle",
|
|
29585
|
+
GIT_COMMITTER_EMAIL: "save@halfcycle.invalid",
|
|
29586
|
+
GIT_COMMITTER_DATE: date5
|
|
29587
|
+
};
|
|
29588
|
+
}
|
|
29589
|
+
var LONGEST_CAP_MS = 2147483647;
|
|
29590
|
+
function wholeMsLeft(ms) {
|
|
29591
|
+
if (!(ms >= 1))
|
|
29592
|
+
return null;
|
|
29593
|
+
return Math.min(Math.floor(ms), LONGEST_CAP_MS);
|
|
29594
|
+
}
|
|
29595
|
+
var SaveGit = class {
|
|
29596
|
+
root;
|
|
29597
|
+
deadline;
|
|
29598
|
+
now;
|
|
29599
|
+
constructor(root, deadline, now) {
|
|
29600
|
+
this.root = root;
|
|
29601
|
+
this.deadline = deadline;
|
|
29602
|
+
this.now = now;
|
|
29603
|
+
}
|
|
29604
|
+
run(args2, env, input) {
|
|
29605
|
+
const left = wholeMsLeft(this.deadline - this.now());
|
|
29606
|
+
if (left === null)
|
|
29607
|
+
return { ok: false, message: "out of time" };
|
|
29608
|
+
return gitForSave(args2, this.root, left, {
|
|
29609
|
+
env: env ?? { GIT_OPTIONAL_LOCKS: "0" },
|
|
29610
|
+
...input === void 0 ? {} : { input }
|
|
29611
|
+
});
|
|
29612
|
+
}
|
|
29613
|
+
};
|
|
29614
|
+
function freshId(folder, newId) {
|
|
29615
|
+
mkdirSync8(folder, { recursive: true, mode: 448 });
|
|
29616
|
+
for (let i = 0; i < 5; i++) {
|
|
29617
|
+
const id = newId?.() ?? defaultId();
|
|
29618
|
+
if (!savedIdSchema.safeParse(id).success)
|
|
29619
|
+
return null;
|
|
29620
|
+
try {
|
|
29621
|
+
mkdirSync8(join14(folder, id), { mode: 448 });
|
|
29622
|
+
return id;
|
|
29623
|
+
} catch (e) {
|
|
29624
|
+
if (e.code !== "EEXIST")
|
|
29625
|
+
return null;
|
|
29626
|
+
}
|
|
29627
|
+
}
|
|
29628
|
+
return null;
|
|
29629
|
+
}
|
|
29630
|
+
function defaultId() {
|
|
29631
|
+
const d = /* @__PURE__ */ new Date();
|
|
29632
|
+
const two = (n) => String(n).padStart(2, "0");
|
|
29633
|
+
const stamp = `${String(d.getUTCFullYear())}${two(d.getUTCMonth() + 1)}${two(d.getUTCDate())}-${two(d.getUTCHours())}${two(d.getUTCMinutes())}${two(d.getUTCSeconds())}`;
|
|
29634
|
+
const rand = [...randomBytes(4)].map((b) => "abcdefghijklmnopqrstuvwxyz0123456789"[b % 36]).join("");
|
|
29635
|
+
return `${stamp}-${rand}`;
|
|
29636
|
+
}
|
|
29637
|
+
function repositoryOf(g) {
|
|
29638
|
+
const r = g.run(["rev-parse", "--git-common-dir"]);
|
|
29639
|
+
if (!r.ok)
|
|
29640
|
+
return null;
|
|
29641
|
+
const dir = r.value.trim();
|
|
29642
|
+
return dir === "" ? null : realOrSelf2(resolve5(g.root, dir));
|
|
29643
|
+
}
|
|
29644
|
+
function markedRepository(dir) {
|
|
29645
|
+
try {
|
|
29646
|
+
const text = readFileSync21(join14(dir, SAVED_REPOSITORY_FILE_NAME), "utf-8").trim();
|
|
29647
|
+
return text === "" ? null : text;
|
|
29648
|
+
} catch {
|
|
29649
|
+
return null;
|
|
29650
|
+
}
|
|
29651
|
+
}
|
|
29652
|
+
function pruneSaves(root, engagementId, ctx = {}) {
|
|
29653
|
+
const folder = savedFolderOf(engagementId, ctx.home);
|
|
29654
|
+
if (folder === null)
|
|
29655
|
+
return;
|
|
29656
|
+
const now = ctx.now ?? Date.now;
|
|
29657
|
+
const g = new SaveGit(root, ctx.deadline ?? now() + SAVE_BUDGET_MS, now);
|
|
29658
|
+
const listed = g.run(["for-each-ref", "--format=%(refname)%00%(committerdate:unix)", SAVED_REF_PREFIX]);
|
|
29659
|
+
if (!listed.ok)
|
|
29660
|
+
return;
|
|
29661
|
+
const refs = /* @__PURE__ */ new Map();
|
|
29662
|
+
for (const line2 of listed.value.split("\n")) {
|
|
29663
|
+
const [name, when] = line2.split("\0");
|
|
29664
|
+
if (name === void 0 || when === void 0 || !name.startsWith(SAVED_REF_PREFIX))
|
|
29665
|
+
continue;
|
|
29666
|
+
refs.set(name.slice(SAVED_REF_PREFIX.length), Number(when) * 1e3);
|
|
29667
|
+
}
|
|
29668
|
+
const repository = repositoryOf(g);
|
|
29669
|
+
let names = [];
|
|
29670
|
+
try {
|
|
29671
|
+
names = readdirSync9(folder);
|
|
29672
|
+
} catch {
|
|
29673
|
+
names = [];
|
|
29674
|
+
}
|
|
29675
|
+
const here = /* @__PURE__ */ new Set();
|
|
29676
|
+
for (const id of names) {
|
|
29677
|
+
if (!savedIdSchema.safeParse(id).success)
|
|
29678
|
+
continue;
|
|
29679
|
+
here.add(id);
|
|
29680
|
+
const dir = join14(folder, id);
|
|
29681
|
+
const st = lstatOrNull(dir);
|
|
29682
|
+
const answer = savedPruneAnswer({
|
|
29683
|
+
refMadeAt: refs.get(id) ?? null,
|
|
29684
|
+
folderChangedAt: st !== null && st.isDirectory() ? st.mtimeMs : null,
|
|
29685
|
+
folderRepository: markedRepository(dir)
|
|
29686
|
+
}, { repository, now: now() });
|
|
29687
|
+
if (answer === "remove-folder")
|
|
29688
|
+
rmSync2(dir, { recursive: true, force: true });
|
|
29689
|
+
else if (answer === "remove-ref-then-folder" && g.run(["update-ref", "-d", savedRefOf(id)]).ok) {
|
|
29690
|
+
rmSync2(dir, { recursive: true, force: true });
|
|
29691
|
+
} else if (answer === "remove-ref")
|
|
29692
|
+
g.run(["update-ref", "-d", savedRefOf(id)]);
|
|
29693
|
+
}
|
|
29694
|
+
for (const [id, madeAt] of refs) {
|
|
29695
|
+
if (here.has(id) || !savedIdSchema.safeParse(id).success)
|
|
29696
|
+
continue;
|
|
29697
|
+
const answer = savedPruneAnswer({ refMadeAt: madeAt, folderChangedAt: null, folderRepository: null }, { repository, now: now() });
|
|
29698
|
+
if (answer === "remove-ref")
|
|
29699
|
+
g.run(["update-ref", "-d", savedRefOf(id)]);
|
|
29700
|
+
}
|
|
29701
|
+
}
|
|
29702
|
+
function lstatOrNull(p) {
|
|
29703
|
+
try {
|
|
29704
|
+
return lstatSync3(p);
|
|
29705
|
+
} catch {
|
|
29706
|
+
return null;
|
|
29707
|
+
}
|
|
29708
|
+
}
|
|
29709
|
+
function realOrSelf2(p) {
|
|
29710
|
+
try {
|
|
29711
|
+
return realpathSync7(p);
|
|
29712
|
+
} catch {
|
|
29713
|
+
return p;
|
|
29714
|
+
}
|
|
29715
|
+
}
|
|
29716
|
+
|
|
29717
|
+
// dist/autonomy-state.js
|
|
29718
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
29719
|
+
import { chmodSync as chmodSync3, mkdirSync as mkdirSync9, readFileSync as readFileSync22, renameSync as renameSync11, rmSync as rmSync3, writeFileSync as writeFileSync13 } from "node:fs";
|
|
29720
|
+
import { homedir as homedir9, platform as platform2 } from "node:os";
|
|
29721
|
+
import { dirname as dirname8, join as join15 } from "node:path";
|
|
29722
|
+
var AUTONOMY_STATE_FILENAME = "autonomy.json";
|
|
29723
|
+
var AUTONOMY_STATE_DEADLINE_MS = 1500;
|
|
29724
|
+
function autonomyStateFilePath(engagementId, home) {
|
|
29725
|
+
return join15(home ?? homedir9(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, AUTONOMY_STATE_FILENAME);
|
|
29726
|
+
}
|
|
29727
|
+
function readCachedAutonomy(engagementId, nowMs, home) {
|
|
29728
|
+
if (!isEngagementId(engagementId))
|
|
29729
|
+
return null;
|
|
29730
|
+
try {
|
|
29731
|
+
const parsed = autonomyStateFileSchema.safeParse(JSON.parse(readFileSync22(autonomyStateFilePath(engagementId, home), "utf-8")));
|
|
29732
|
+
if (!parsed.success || !parsed.data.on)
|
|
29733
|
+
return null;
|
|
29734
|
+
const age = nowMs - Date.parse(parsed.data.readAt);
|
|
29735
|
+
return Number.isFinite(age) && age >= 0 && age < AUTONOMY_STATE_FILE_MAX_AGE_MS ? parsed.data : null;
|
|
29736
|
+
} catch {
|
|
29737
|
+
return null;
|
|
29738
|
+
}
|
|
29739
|
+
}
|
|
29740
|
+
function confirmMayPublishNow(opts) {
|
|
29741
|
+
if (!isEngagementId(opts.engagementId) || CREDENTIAL_PLACEHOLDERS.includes(opts.bearer))
|
|
29742
|
+
return false;
|
|
29743
|
+
if (opts.timeoutMs <= 0)
|
|
29744
|
+
return false;
|
|
29745
|
+
try {
|
|
29746
|
+
const url2 = `${opts.controlOrigin}/engagements/${encodeURIComponent(opts.engagementId)}/autonomy/state`;
|
|
29747
|
+
const r = spawnSync2(process.execPath, ["--input-type=module", "-e", LIVE_READ_PROGRAM], {
|
|
29748
|
+
input: JSON.stringify({ url: url2, bearer: opts.bearer, ms: Math.max(1, opts.timeoutMs - 250) }),
|
|
29749
|
+
encoding: "utf-8",
|
|
29750
|
+
timeout: opts.timeoutMs,
|
|
29751
|
+
windowsHide: true
|
|
29752
|
+
});
|
|
29753
|
+
if (r.status !== 0 || typeof r.stdout !== "string")
|
|
29754
|
+
return false;
|
|
29755
|
+
const parsed = autonomyStateSchema.safeParse(JSON.parse(r.stdout));
|
|
29756
|
+
return parsed.success && parsed.data.on && parsed.data.mayPublish;
|
|
29757
|
+
} catch {
|
|
29758
|
+
return false;
|
|
28111
29759
|
}
|
|
28112
29760
|
}
|
|
28113
|
-
|
|
28114
|
-
|
|
29761
|
+
var LIVE_READ_PROGRAM = [
|
|
29762
|
+
"let t='';process.stdin.setEncoding('utf8');",
|
|
29763
|
+
"process.stdin.on('data',(c)=>{t+=c;});",
|
|
29764
|
+
"process.stdin.on('end',async()=>{try{const o=JSON.parse(t);",
|
|
29765
|
+
"const r=await fetch(o.url,{method:'GET',headers:{Authorization:'Bearer '+o.bearer},redirect:'manual',signal:AbortSignal.timeout(o.ms)});",
|
|
29766
|
+
"if(r.status<200||r.status>299)process.exit(1);process.stdout.write(JSON.stringify(await r.json()));process.exit(0);",
|
|
29767
|
+
"}catch{process.exit(1);}});"
|
|
29768
|
+
].join("");
|
|
29769
|
+
function deleteAutonomyStateFile(engagementId, home) {
|
|
29770
|
+
try {
|
|
29771
|
+
rmSync3(autonomyStateFilePath(engagementId, home), { force: true });
|
|
29772
|
+
} catch {
|
|
29773
|
+
}
|
|
28115
29774
|
}
|
|
28116
|
-
function
|
|
29775
|
+
function writeAutonomyStateFile(engagementId, file2, home) {
|
|
29776
|
+
const target = autonomyStateFilePath(engagementId, home);
|
|
29777
|
+
const temp = join15(dirname8(target), `.${AUTONOMY_STATE_FILENAME}.${process.pid}.tmp`);
|
|
28117
29778
|
try {
|
|
28118
|
-
|
|
28119
|
-
|
|
28120
|
-
|
|
28121
|
-
|
|
28122
|
-
|
|
29779
|
+
mkdirSync9(dirname8(target), { recursive: true, mode: 448 });
|
|
29780
|
+
writeFileSync13(temp, `${JSON.stringify(file2)}
|
|
29781
|
+
`, { mode: 384 });
|
|
29782
|
+
if (platform2() !== "win32")
|
|
29783
|
+
chmodSync3(temp, 384);
|
|
29784
|
+
renameSync11(temp, target);
|
|
29785
|
+
return true;
|
|
28123
29786
|
} catch {
|
|
28124
|
-
|
|
29787
|
+
try {
|
|
29788
|
+
rmSync3(temp, { force: true });
|
|
29789
|
+
} catch {
|
|
29790
|
+
}
|
|
29791
|
+
return false;
|
|
28125
29792
|
}
|
|
28126
29793
|
}
|
|
28127
|
-
function
|
|
28128
|
-
|
|
28129
|
-
|
|
28130
|
-
|
|
28131
|
-
|
|
28132
|
-
|
|
28133
|
-
|
|
28134
|
-
|
|
28135
|
-
|
|
28136
|
-
|
|
28137
|
-
|
|
28138
|
-
|
|
29794
|
+
async function readAutonomyStateFromControl(opts) {
|
|
29795
|
+
const controller = new AbortController();
|
|
29796
|
+
const timer = setTimeout(() => controller.abort(), opts.deadlineMs);
|
|
29797
|
+
try {
|
|
29798
|
+
const url2 = `${opts.controlOrigin}/engagements/${encodeURIComponent(opts.engagementId)}/autonomy/state`;
|
|
29799
|
+
const res = await opts.fetchFn(url2, {
|
|
29800
|
+
method: "GET",
|
|
29801
|
+
headers: { Authorization: `Bearer ${opts.bearer}` },
|
|
29802
|
+
redirect: "manual",
|
|
29803
|
+
signal: controller.signal
|
|
29804
|
+
});
|
|
29805
|
+
if (res.status < 200 || res.status > 299)
|
|
29806
|
+
return void 0;
|
|
29807
|
+
const parsed = autonomyStateSchema.safeParse(await res.json());
|
|
29808
|
+
return parsed.success ? parsed.data : void 0;
|
|
29809
|
+
} catch {
|
|
29810
|
+
return void 0;
|
|
29811
|
+
} finally {
|
|
29812
|
+
clearTimeout(timer);
|
|
29813
|
+
}
|
|
28139
29814
|
}
|
|
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";
|
|
29815
|
+
async function cacheAutonomySetting(opts) {
|
|
29816
|
+
const { engagementId, home } = opts;
|
|
29817
|
+
if (!isEngagementId(engagementId))
|
|
29818
|
+
return;
|
|
29819
|
+
const bearer = opts.bearer;
|
|
29820
|
+
const usable = bearer !== void 0 && !CREDENTIAL_PLACEHOLDERS.includes(bearer) && opts.deadlineMs > 0;
|
|
29821
|
+
const state = usable ? await readAutonomyStateFromControl({
|
|
29822
|
+
controlOrigin: opts.controlOrigin,
|
|
29823
|
+
engagementId,
|
|
29824
|
+
bearer,
|
|
29825
|
+
fetchFn: opts.fetchFn,
|
|
29826
|
+
deadlineMs: opts.deadlineMs
|
|
29827
|
+
}) : void 0;
|
|
29828
|
+
if (state === void 0) {
|
|
29829
|
+
deleteAutonomyStateFile(engagementId, home);
|
|
29830
|
+
return;
|
|
29831
|
+
}
|
|
29832
|
+
const file2 = autonomyStateFileSchema.safeParse({ ...state, readAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
29833
|
+
if (!file2.success || !writeAutonomyStateFile(engagementId, file2.data, home)) {
|
|
29834
|
+
deleteAutonomyStateFile(engagementId, home);
|
|
28170
29835
|
}
|
|
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
29836
|
}
|
|
28189
29837
|
|
|
28190
29838
|
// dist/hooks/pre-tool-use.js
|
|
28191
|
-
import {
|
|
29839
|
+
import { performance } from "node:perf_hooks";
|
|
29840
|
+
import { homedir as homedir10 } from "node:os";
|
|
28192
29841
|
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.";
|
|
29842
|
+
var PRE_TOOL_USE_LIMIT_MS = (HALFCYCLE_HOOK_WIRING.find((e) => e.event === "PreToolUse")?.timeout ?? 10) * 1e3;
|
|
29843
|
+
var AUTONOMY_REPLY_MARGIN_MS = 1e3;
|
|
29844
|
+
var MAY_PUBLISH_READ_MS = 3e3;
|
|
28193
29845
|
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
29846
|
var CHECKED = new Set(CHECKED_TOOLS);
|
|
29847
|
+
var RECORDED_ONLY = new Set(RECORDED_ONLY_TOOLS);
|
|
28195
29848
|
function runPreToolUse(deps) {
|
|
28196
29849
|
const input = parseObject(deps.input);
|
|
28197
|
-
|
|
29850
|
+
const at = clockOf(deps)();
|
|
29851
|
+
const stamped = { ...deps, now: () => at };
|
|
29852
|
+
recordTimeEvent("action", input ?? {}, stamped);
|
|
29853
|
+
recordOwnShell(input, at, { ...deps.liveness?.dir === void 0 ? {} : { dir: deps.liveness.dir } });
|
|
28198
29854
|
const seen = recordLiveness(input ?? {}, deps.liveness);
|
|
28199
29855
|
const paceDeps = {
|
|
28200
29856
|
...deps.pace ?? {},
|
|
@@ -28204,6 +29860,10 @@ function runPreToolUse(deps) {
|
|
|
28204
29860
|
};
|
|
28205
29861
|
if (seen?.firstSeen === true && input !== null && typeof input["agent_id"] === "string")
|
|
28206
29862
|
retirePendingStart(input, paceDeps);
|
|
29863
|
+
const { mode, autonomy } = answerModeOf(input, deps, at);
|
|
29864
|
+
const toolName = input === null ? void 0 : stringOf(input, "tool_name");
|
|
29865
|
+
if (toolName !== void 0 && RECORDED_ONLY.has(toolName))
|
|
29866
|
+
return recordedOnlyReply(mode);
|
|
28207
29867
|
const call = input === null ? null : toolCallOf(input);
|
|
28208
29868
|
if (call === null || !CHECKED.has(call.toolName))
|
|
28209
29869
|
return unreadable(deps, input);
|
|
@@ -28226,7 +29886,6 @@ function runPreToolUse(deps) {
|
|
|
28226
29886
|
}
|
|
28227
29887
|
const own = ownCopyOf(input, call, deps);
|
|
28228
29888
|
try {
|
|
28229
|
-
const mode = answerModeFor(input["permission_mode"]);
|
|
28230
29889
|
if (own.kind === "refused") {
|
|
28231
29890
|
const staked = [...answer.silent ? [] : answer.staked, own.facts];
|
|
28232
29891
|
if (decide(call, staked, mode) === "refuse") {
|
|
@@ -28235,6 +29894,11 @@ function runPreToolUse(deps) {
|
|
|
28235
29894
|
`;
|
|
28236
29895
|
}
|
|
28237
29896
|
}
|
|
29897
|
+
if (mode === "autonomous") {
|
|
29898
|
+
const sendsSaves = call.toolName === "Bash" && typeof call.toolInput["command"] === "string" && pushesSavedRefs(call.toolInput["command"]);
|
|
29899
|
+
if (!answer.silent || sendsSaves)
|
|
29900
|
+
return autonomousReply(call, answer.silent ? [] : answer.staked, sendsSaves, mode, autonomy, deps);
|
|
29901
|
+
}
|
|
28238
29902
|
if (answer.silent) {
|
|
28239
29903
|
if (call.toolName === PACE_HELD_TOOL) {
|
|
28240
29904
|
const held = paceHoldText(input, call.toolInput, paceDeps);
|
|
@@ -28255,12 +29919,100 @@ function runPreToolUse(deps) {
|
|
|
28255
29919
|
return "";
|
|
28256
29920
|
}
|
|
28257
29921
|
}
|
|
29922
|
+
function clockOf(deps) {
|
|
29923
|
+
return deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
29924
|
+
}
|
|
29925
|
+
function answerModeOf(input, deps, at) {
|
|
29926
|
+
const engagementId = readConfig(deps.env ?? process.env).config?.guardEngagementId;
|
|
29927
|
+
const cached2 = engagementId === void 0 ? null : readCachedAutonomy(engagementId, at.getTime(), deps.home);
|
|
29928
|
+
if (engagementId !== void 0 && cached2 !== null)
|
|
29929
|
+
return { mode: "autonomous", autonomy: { engagementId, mayPublish: cached2.mayPublish } };
|
|
29930
|
+
return { mode: answerModeFor(input === null ? void 0 : input["permission_mode"]), autonomy: null };
|
|
29931
|
+
}
|
|
29932
|
+
function autonomousReply(call, staked, sendsSaves, mode, autonomy, deps) {
|
|
29933
|
+
try {
|
|
29934
|
+
return autonomousAnswer(call, staked, sendsSaves, mode, autonomy, deps);
|
|
29935
|
+
} catch {
|
|
29936
|
+
return `${preToolUseReplyText("refuse", autonomyA12(typeSentenceFor(staked, sendsSaves), AUTONOMY_SAVE_FAILED_REASON))}
|
|
29937
|
+
`;
|
|
29938
|
+
}
|
|
29939
|
+
}
|
|
29940
|
+
function autonomousAnswer(call, staked, sendsSaves, mode, autonomy, deps) {
|
|
29941
|
+
const startedAt = deps.autonomy?.startedAtMs ?? performance.timeOrigin;
|
|
29942
|
+
const left = () => Math.floor(startedAt + PRE_TOOL_USE_LIMIT_MS - AUTONOMY_REPLY_MARGIN_MS - Date.now());
|
|
29943
|
+
const mayPublishNow = () => {
|
|
29944
|
+
if (!autonomy.mayPublish)
|
|
29945
|
+
return false;
|
|
29946
|
+
if (deps.autonomy?.mayPublishNow !== void 0)
|
|
29947
|
+
return deps.autonomy.mayPublishNow();
|
|
29948
|
+
const env = deps.env ?? process.env;
|
|
29949
|
+
const config2 = readConfig(env).config;
|
|
29950
|
+
if (config2 === null)
|
|
29951
|
+
return false;
|
|
29952
|
+
return confirmMayPublishNow({
|
|
29953
|
+
controlOrigin: readControlOrigin(env).origin,
|
|
29954
|
+
engagementId: autonomy.engagementId,
|
|
29955
|
+
bearer: config2.guardServiceToken,
|
|
29956
|
+
timeoutMs: Math.min(MAY_PUBLISH_READ_MS, left())
|
|
29957
|
+
});
|
|
29958
|
+
};
|
|
29959
|
+
const decided = decide(call, sendsSaves ? [...staked, { type: "rewrite-shared-history" }] : staked, mode, mayPublishNow);
|
|
29960
|
+
const refuse = (text) => `${preToolUseReplyText("refuse", text)}
|
|
29961
|
+
`;
|
|
29962
|
+
if (decided === "ask" || decided === "refuse")
|
|
29963
|
+
return refuse(autonomyA12(typeSentenceFor([]), AUTONOMY_COULD_NOT_TELL_REASON));
|
|
29964
|
+
switch (decided.autonomous) {
|
|
29965
|
+
case "waits-for-owner":
|
|
29966
|
+
return refuse(autonomyA10(typeSentenceFor(staked, sendsSaves), decided.item));
|
|
29967
|
+
case "never-under-autonomy":
|
|
29968
|
+
return refuse(autonomyA11(typeSentenceFor(staked, sendsSaves)));
|
|
29969
|
+
case "could-not-tell":
|
|
29970
|
+
return refuse(autonomyA12(typeSentenceFor([]), AUTONOMY_COULD_NOT_TELL_REASON));
|
|
29971
|
+
case "let-through":
|
|
29972
|
+
return "";
|
|
29973
|
+
case "save-then-let-through": {
|
|
29974
|
+
const kept = staked.filter((f) => savedByAutonomy(f.type));
|
|
29975
|
+
const what = savedWhatFor(kept);
|
|
29976
|
+
const budgetMs = Math.min(SAVE_BUDGET_MS, left());
|
|
29977
|
+
const saved = budgetMs <= 0 ? { saved: false, reason: AUTONOMY_SAVE_FAILED_REASON } : saveFirst(kept, {
|
|
29978
|
+
engagementId: autonomy.engagementId,
|
|
29979
|
+
what,
|
|
29980
|
+
budgetMs,
|
|
29981
|
+
...deps.home === void 0 ? {} : { home: deps.home },
|
|
29982
|
+
...deps.autonomy?.newId === void 0 ? {} : { newId: deps.autonomy.newId },
|
|
29983
|
+
...deps.autonomy?.beforeRef === void 0 ? {} : { beforeRef: deps.autonomy.beforeRef }
|
|
29984
|
+
});
|
|
29985
|
+
if (!saved.saved)
|
|
29986
|
+
return refuse(autonomyA12(typeSentenceFor(saved.reason === AUTONOMY_COULD_NOT_TELL_REASON ? [] : staked), saved.reason));
|
|
29987
|
+
return `${preToolUseNoticeText(autonomyA9(what, saved.id))}
|
|
29988
|
+
`;
|
|
29989
|
+
}
|
|
29990
|
+
default: {
|
|
29991
|
+
const unhandled = decided;
|
|
29992
|
+
return unhandled;
|
|
29993
|
+
}
|
|
29994
|
+
}
|
|
29995
|
+
}
|
|
29996
|
+
function recordedOnlyReply(mode) {
|
|
29997
|
+
switch (mode) {
|
|
29998
|
+
case "questions-on":
|
|
29999
|
+
case "questions-off":
|
|
30000
|
+
return "";
|
|
30001
|
+
case "autonomous":
|
|
30002
|
+
return `${preToolUseReplyText("refuse", AUTONOMY_A18)}
|
|
30003
|
+
`;
|
|
30004
|
+
default: {
|
|
30005
|
+
const unhandled = mode;
|
|
30006
|
+
return unhandled;
|
|
30007
|
+
}
|
|
30008
|
+
}
|
|
30009
|
+
}
|
|
28258
30010
|
function ownCopyOf(input, call, deps) {
|
|
28259
30011
|
try {
|
|
28260
30012
|
const walk2 = deps.walk ?? deps.liveness?.walk;
|
|
28261
30013
|
return judgeOwnCopy(input, call, {
|
|
28262
30014
|
projectDir: deps.projectDir ?? projectFolder(deps.env ?? process.env),
|
|
28263
|
-
home: deps.home ??
|
|
30015
|
+
home: deps.home ?? homedir10(),
|
|
28264
30016
|
...walk2 === void 0 ? {} : { walk: walk2 },
|
|
28265
30017
|
...deps.liveness === void 0 ? {} : { liveness: deps.liveness }
|
|
28266
30018
|
});
|
|
@@ -28300,6 +30052,8 @@ function unreadable(deps, input) {
|
|
|
28300
30052
|
}
|
|
28301
30053
|
|
|
28302
30054
|
// dist/hooks/session-diff.js
|
|
30055
|
+
import { unlinkSync as unlinkSync9 } from "node:fs";
|
|
30056
|
+
import { join as join16 } from "node:path";
|
|
28303
30057
|
var STOP_HOOK_EVENT = {
|
|
28304
30058
|
stop: "Stop",
|
|
28305
30059
|
"subagent-stop": "SubagentStop"
|
|
@@ -28351,6 +30105,7 @@ async function evaluateSession(input, kind, reply, readSeat) {
|
|
|
28351
30105
|
}
|
|
28352
30106
|
const repoRoot = repoRootResult.value;
|
|
28353
30107
|
const sessionId = input.session_id;
|
|
30108
|
+
const folder = realPathOf(repoRoot);
|
|
28354
30109
|
orphanSweep(repoRoot, (ref) => refExists(ref, repoRoot), sessionId, Date.now(), whichCopy(process.cwd())?.common ?? null);
|
|
28355
30110
|
const base = resolveDiffBase2(repoRoot, sessionId);
|
|
28356
30111
|
const diffBase = base.kind;
|
|
@@ -28359,9 +30114,16 @@ async function evaluateSession(input, kind, reply, readSeat) {
|
|
|
28359
30114
|
reply.notice(localFaultText(`Could not get session diff: ${collected.message}`));
|
|
28360
30115
|
return;
|
|
28361
30116
|
}
|
|
28362
|
-
const
|
|
28363
|
-
const
|
|
30117
|
+
const tracked = new Set(collected.files);
|
|
30118
|
+
const candidates = ownEditedFiles(repoRoot, sessionId).filter((relPath) => !tracked.has(relPath));
|
|
30119
|
+
const untracked = getUntrackedFiles(repoRoot, candidates);
|
|
30120
|
+
const created = untracked.ok ? untracked.value.filter((relPath) => !tracked.has(relPath)) : [];
|
|
30121
|
+
const getDiff = (relPath) => tracked.has(relPath) ? collected.getDiff(relPath) : getSingleFileDiff(join16(repoRoot, relPath), repoRoot);
|
|
30122
|
+
const { evaluated } = partitionInstallerOutput(collected.files);
|
|
30123
|
+
const { evaluated: createdEvaluated } = partitionInstallerOutput(created);
|
|
30124
|
+
const changedFiles2 = [...ownChangedFiles(repoRoot, evaluated, sessionId), ...createdEvaluated];
|
|
28364
30125
|
if (changedFiles2.length === 0) {
|
|
30126
|
+
queueTurnEnd(keepBooks(sessionId, folder, { kind: "no-own-changes" }, kind === "stop"), reply);
|
|
28365
30127
|
return;
|
|
28366
30128
|
}
|
|
28367
30129
|
const fileDiffEntries = changedFiles2.map((relPath) => ({
|
|
@@ -28372,6 +30134,7 @@ async function evaluateSession(input, kind, reply, readSeat) {
|
|
|
28372
30134
|
const digest = digestChangeState(baseKey, fileDiffEntries);
|
|
28373
30135
|
const prior = readDedupe(repoRoot, sessionId);
|
|
28374
30136
|
if (prior && prior.base === baseKey && prior.digest === digest) {
|
|
30137
|
+
queueTurnEnd(keepBooks(sessionId, folder, { kind: "unchanged" }, kind === "stop"), reply);
|
|
28375
30138
|
return;
|
|
28376
30139
|
}
|
|
28377
30140
|
const context = loadChangeSetContext(repoRoot);
|
|
@@ -28450,30 +30213,56 @@ async function evaluateSession(input, kind, reply, readSeat) {
|
|
|
28450
30213
|
warn: (msg) => process.stderr.write(msg + "\n")
|
|
28451
30214
|
});
|
|
28452
30215
|
writeDedupe(repoRoot, sessionId, { base: baseKey, digest });
|
|
28453
|
-
actOnEnvelope3(clientResult.envelope, kind, input.stop_hook_active === true, reply);
|
|
30216
|
+
actOnEnvelope3(clientResult.envelope, kind, input.stop_hook_active === true, reply, sessionId, folder);
|
|
30217
|
+
}
|
|
30218
|
+
function keepBooks(sessionId, folder, outcome, topLevel) {
|
|
30219
|
+
const books = () => {
|
|
30220
|
+
const before = readFindings(sessionId);
|
|
30221
|
+
const { file: file2, news } = turnEndBookkeeping(before ?? emptyFindings(""), folder, outcome, topLevel, /* @__PURE__ */ new Date());
|
|
30222
|
+
if (before !== null && JSON.stringify(before) !== JSON.stringify(file2)) {
|
|
30223
|
+
if (file2.findings.length > 0)
|
|
30224
|
+
writeFindings(file2);
|
|
30225
|
+
else
|
|
30226
|
+
removeFindingsFile(sessionId);
|
|
30227
|
+
}
|
|
30228
|
+
return turnEndMessage(news);
|
|
30229
|
+
};
|
|
30230
|
+
const locked = withFindingsLock(sessionId, books);
|
|
30231
|
+
return locked === void 0 ? books() : locked;
|
|
28454
30232
|
}
|
|
28455
|
-
function
|
|
28456
|
-
const
|
|
28457
|
-
|
|
28458
|
-
${notRun}` : "";
|
|
28459
|
-
if (envelope.guardsFired.length === 0) {
|
|
28460
|
-
if (notRun)
|
|
28461
|
-
reply.notice(notRun);
|
|
30233
|
+
function removeFindingsFile(sessionId) {
|
|
30234
|
+
const path = findingsPath(sessionId);
|
|
30235
|
+
if (path === null)
|
|
28462
30236
|
return;
|
|
30237
|
+
try {
|
|
30238
|
+
unlinkSync9(path);
|
|
30239
|
+
} catch {
|
|
28463
30240
|
}
|
|
28464
|
-
|
|
28465
|
-
|
|
30241
|
+
}
|
|
30242
|
+
function queueTurnEnd(words, reply) {
|
|
30243
|
+
if (words !== null)
|
|
30244
|
+
reply.notice(words);
|
|
30245
|
+
}
|
|
30246
|
+
function actOnEnvelope3(envelope, kind, neverBlock, reply, sessionId, folder) {
|
|
30247
|
+
const notRun = notRunReport(envelope.notRun);
|
|
30248
|
+
const outcome = {
|
|
30249
|
+
kind: "evaluated",
|
|
30250
|
+
fired: envelope.guardsFired,
|
|
30251
|
+
notRun: (envelope.notRun ?? []).map((n) => n.patternRef)
|
|
30252
|
+
};
|
|
30253
|
+
const topLevel = kind === "stop";
|
|
28466
30254
|
if (envelope.blocking && !neverBlock) {
|
|
28467
|
-
|
|
28468
|
-
|
|
28469
|
-
|
|
28470
|
-
|
|
28471
|
-
|
|
28472
|
-
|
|
28473
|
-
|
|
28474
|
-
reply.notice(`[Halfcycle] Guard warning at ${where}: ${envelope.explanation}
|
|
28475
|
-
${guardLines}${notRunBlock}`);
|
|
30255
|
+
keepBooks(sessionId, folder, outcome, false);
|
|
30256
|
+
const reason = blockReason(envelope.guardsFired.map((g) => g.explanation));
|
|
30257
|
+
reply.block(notRun ? `${reason}
|
|
30258
|
+
${notRun}` : reason, stoppedMessage(blockingExplanations(envelope.guardsFired)));
|
|
30259
|
+
}
|
|
30260
|
+
if (envelope.blocking && neverBlock) {
|
|
30261
|
+
reply.notice(couldNotFixMessage(blockingExplanations(envelope.guardsFired)));
|
|
28476
30262
|
}
|
|
30263
|
+
queueTurnEnd(keepBooks(sessionId, folder, outcome, topLevel), reply);
|
|
30264
|
+
if (notRun)
|
|
30265
|
+
reply.notice(notRun);
|
|
28477
30266
|
}
|
|
28478
30267
|
|
|
28479
30268
|
// dist/hooks/stdin.js
|
|
@@ -28492,9 +30281,9 @@ async function readHookStdin() {
|
|
|
28492
30281
|
}
|
|
28493
30282
|
}
|
|
28494
30283
|
function readStdin2() {
|
|
28495
|
-
return new Promise((
|
|
30284
|
+
return new Promise((resolve8) => {
|
|
28496
30285
|
if (process.stdin.isTTY) {
|
|
28497
|
-
|
|
30286
|
+
resolve8("");
|
|
28498
30287
|
return;
|
|
28499
30288
|
}
|
|
28500
30289
|
const chunks = [];
|
|
@@ -28504,7 +30293,7 @@ function readStdin2() {
|
|
|
28504
30293
|
return;
|
|
28505
30294
|
settled = true;
|
|
28506
30295
|
clearTimeout(timer);
|
|
28507
|
-
|
|
30296
|
+
resolve8(Buffer.concat(chunks).toString("utf-8"));
|
|
28508
30297
|
};
|
|
28509
30298
|
const timer = setTimeout(done, HOOK_STDIN_GRACE_MS);
|
|
28510
30299
|
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
@@ -28584,6 +30373,22 @@ function matchScopePhraseShape(shape, shown2) {
|
|
|
28584
30373
|
return { kind: "show-first", act: "send-back" };
|
|
28585
30374
|
}
|
|
28586
30375
|
|
|
30376
|
+
// dist/hooks/autonomy-phrase.js
|
|
30377
|
+
var AUTONOMY_ON = /^autonomy on$/i;
|
|
30378
|
+
var AUTONOMY_OFF = /^autonomy off$/i;
|
|
30379
|
+
function autonomyPhraseShape(prompt) {
|
|
30380
|
+
if (typeof prompt !== "string")
|
|
30381
|
+
return null;
|
|
30382
|
+
const line2 = prompt.trim();
|
|
30383
|
+
if (LINE_BREAK.test(line2))
|
|
30384
|
+
return null;
|
|
30385
|
+
if (AUTONOMY_ON.test(line2))
|
|
30386
|
+
return "on";
|
|
30387
|
+
if (AUTONOMY_OFF.test(line2))
|
|
30388
|
+
return "off";
|
|
30389
|
+
return null;
|
|
30390
|
+
}
|
|
30391
|
+
|
|
28587
30392
|
// dist/hooks/review-phrase.js
|
|
28588
30393
|
var VERBS = [
|
|
28589
30394
|
[/^accept /i, "accept"],
|
|
@@ -28650,20 +30455,20 @@ function matchReviewPhraseShape(shape, reviews) {
|
|
|
28650
30455
|
}
|
|
28651
30456
|
|
|
28652
30457
|
// dist/hooks/shown-record.js
|
|
28653
|
-
import { chmodSync as
|
|
28654
|
-
import { homedir as
|
|
28655
|
-
import { join as
|
|
30458
|
+
import { chmodSync as chmodSync4, readFileSync as readFileSync23, renameSync as renameSync12, unlinkSync as unlinkSync10, writeFileSync as writeFileSync14 } from "node:fs";
|
|
30459
|
+
import { homedir as homedir11, platform as platform3 } from "node:os";
|
|
30460
|
+
import { join as join17 } from "node:path";
|
|
28656
30461
|
function recordDir(engagementId, home) {
|
|
28657
30462
|
if (!isEngagementId(engagementId))
|
|
28658
30463
|
return null;
|
|
28659
|
-
return
|
|
30464
|
+
return join17(home ?? homedir11(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId);
|
|
28660
30465
|
}
|
|
28661
30466
|
function readShownRecord(engagementId, home) {
|
|
28662
30467
|
const dir = recordDir(engagementId, home);
|
|
28663
30468
|
if (dir === null)
|
|
28664
30469
|
return { entries: [], reviews: [] };
|
|
28665
30470
|
try {
|
|
28666
|
-
return parseShownRecord(
|
|
30471
|
+
return parseShownRecord(readFileSync23(join17(dir, SHOWN_PLANS_FILENAME), "utf-8"));
|
|
28667
30472
|
} catch {
|
|
28668
30473
|
return { entries: [], reviews: [] };
|
|
28669
30474
|
}
|
|
@@ -28684,24 +30489,24 @@ function moveListedReviewForward(engagementId, home, listed, reviewVersion, deci
|
|
|
28684
30489
|
const dir = recordDir(engagementId, home);
|
|
28685
30490
|
if (dir === null)
|
|
28686
30491
|
return false;
|
|
28687
|
-
const path =
|
|
28688
|
-
const current = parseShownRecord(
|
|
30492
|
+
const path = join17(dir, SHOWN_PLANS_FILENAME);
|
|
30493
|
+
const current = parseShownRecord(readFileSync23(path, "utf-8"));
|
|
28689
30494
|
const at = current.reviews.findIndex((r) => r.phase === listed.phase && r.shownAt === listed.shownAt && r.reviewVersion === listed.reviewVersion);
|
|
28690
30495
|
if (at === -1)
|
|
28691
30496
|
return false;
|
|
28692
30497
|
const acts = new Map(decided.map((d) => [d.part, d.act]));
|
|
28693
30498
|
const reviews = current.reviews.map((review, i) => i === at ? { ...review, reviewVersion, parts: review.parts.map((p) => movedPart(p, acts.get(p.part))) } : review);
|
|
28694
30499
|
const body = serialiseShownRecord({ entries: current.entries, reviews });
|
|
28695
|
-
temp =
|
|
28696
|
-
|
|
28697
|
-
if (
|
|
28698
|
-
|
|
28699
|
-
|
|
30500
|
+
temp = join17(dir, `.${SHOWN_PLANS_FILENAME}.${process.pid}.tmp`);
|
|
30501
|
+
writeFileSync14(temp, body, { mode: 384 });
|
|
30502
|
+
if (platform3() !== "win32")
|
|
30503
|
+
chmodSync4(temp, 384);
|
|
30504
|
+
renameSync12(temp, path);
|
|
28700
30505
|
return true;
|
|
28701
30506
|
} catch {
|
|
28702
30507
|
if (temp !== null) {
|
|
28703
30508
|
try {
|
|
28704
|
-
|
|
30509
|
+
unlinkSync10(temp);
|
|
28705
30510
|
} catch {
|
|
28706
30511
|
return false;
|
|
28707
30512
|
}
|
|
@@ -28996,15 +30801,59 @@ function reviewLineForAnswer(context, answer) {
|
|
|
28996
30801
|
}
|
|
28997
30802
|
}
|
|
28998
30803
|
}
|
|
30804
|
+
var AUTONOMY_NOTHING_CHANGED = "Nothing changed";
|
|
30805
|
+
function autonomyNotAttendedLine() {
|
|
30806
|
+
return line(`${AUTONOMY_NOTHING_CHANGED}: turning this on or off by typing counts only when a person types it into an open session.`);
|
|
30807
|
+
}
|
|
30808
|
+
function autonomyNotSignedInLine() {
|
|
30809
|
+
return line(`${AUTONOMY_NOTHING_CHANGED}: this project is not signed in on this computer. ${HOOK_REMEDY}`);
|
|
30810
|
+
}
|
|
30811
|
+
function autonomyNotOwnerLine() {
|
|
30812
|
+
return line(`${AUTONOMY_NOTHING_CHANGED}: only the project's owner can turn this on or off.`);
|
|
30813
|
+
}
|
|
30814
|
+
function autonomyNoOwnerLine() {
|
|
30815
|
+
return line(`${AUTONOMY_NOTHING_CHANGED}: this project has no owner, so nobody can turn this on or off.`);
|
|
30816
|
+
}
|
|
30817
|
+
function autonomyNotOfferedLine() {
|
|
30818
|
+
return line(`${AUTONOMY_NOTHING_CHANGED}. ${TYPED_APPROVAL_OFF_SENTENCE}`);
|
|
30819
|
+
}
|
|
30820
|
+
function autonomyCannotReachLine() {
|
|
30821
|
+
return line(`${AUTONOMY_NOTHING_CHANGED}: Halfcycle could not be reached just now. Type the line again in a minute.`);
|
|
30822
|
+
}
|
|
30823
|
+
function autonomyLineForAnswer(answer) {
|
|
30824
|
+
switch (answer.outcome) {
|
|
30825
|
+
case "recorded":
|
|
30826
|
+
return line(answer.on ? AUTONOMY_A4 : AUTONOMY_A5);
|
|
30827
|
+
case "not-owner":
|
|
30828
|
+
return autonomyNotOwnerLine();
|
|
30829
|
+
case "no-owner":
|
|
30830
|
+
return autonomyNoOwnerLine();
|
|
30831
|
+
case "not-offered":
|
|
30832
|
+
return autonomyNotOfferedLine();
|
|
30833
|
+
case "unavailable":
|
|
30834
|
+
return autonomyCannotReachLine();
|
|
30835
|
+
default: {
|
|
30836
|
+
const unreachable = answer;
|
|
30837
|
+
return unreachable;
|
|
30838
|
+
}
|
|
30839
|
+
}
|
|
30840
|
+
}
|
|
28999
30841
|
|
|
29000
30842
|
// dist/hooks/user-prompt-submit.js
|
|
29001
30843
|
var CHAT_REQUEST_TIMEOUT_MS = 6e3;
|
|
29002
30844
|
var SHOWN_ENTRY_MAX_AGE_MS = 60 * 60 * 1e3;
|
|
29003
30845
|
async function runUserPromptSubmit(opts) {
|
|
29004
30846
|
let act = null;
|
|
30847
|
+
let autonomyAct = null;
|
|
29005
30848
|
const review = { recognised: false };
|
|
29006
30849
|
try {
|
|
29007
30850
|
const prompt = promptOf(opts.input);
|
|
30851
|
+
const autonomyShape = autonomyPhraseShape(prompt);
|
|
30852
|
+
if (autonomyShape !== null) {
|
|
30853
|
+
autonomyAct = autonomyShape;
|
|
30854
|
+
return `${await decideAutonomy(autonomyShape, opts)}
|
|
30855
|
+
`;
|
|
30856
|
+
}
|
|
29008
30857
|
const reviewShape = reviewPhraseShape(prompt);
|
|
29009
30858
|
if (reviewShape !== null) {
|
|
29010
30859
|
const out = await decideReview(reviewShape, opts, review);
|
|
@@ -29018,6 +30867,9 @@ async function runUserPromptSubmit(opts) {
|
|
|
29018
30867
|
return `${await decide2(shape, opts)}
|
|
29019
30868
|
`;
|
|
29020
30869
|
} catch {
|
|
30870
|
+
if (autonomyAct !== null)
|
|
30871
|
+
return `${autonomyCannotReachLine()}
|
|
30872
|
+
`;
|
|
29021
30873
|
if (review.recognised)
|
|
29022
30874
|
return `${reviewCannotReachLine()}
|
|
29023
30875
|
`;
|
|
@@ -29025,6 +30877,44 @@ async function runUserPromptSubmit(opts) {
|
|
|
29025
30877
|
`;
|
|
29026
30878
|
}
|
|
29027
30879
|
}
|
|
30880
|
+
async function decideAutonomy(act, opts) {
|
|
30881
|
+
const env = opts.env ?? process.env;
|
|
30882
|
+
if (!readSessionAttended(env))
|
|
30883
|
+
return autonomyNotAttendedLine();
|
|
30884
|
+
const read = readConfig(env);
|
|
30885
|
+
if (read.config === null) {
|
|
30886
|
+
return readNeverConfiguredReason(env) === "not-this-account" ? autonomyNotOwnerLine() : autonomyNotSignedInLine();
|
|
30887
|
+
}
|
|
30888
|
+
const { guardServiceToken, guardEngagementId } = read.config;
|
|
30889
|
+
return postAutonomy(act, guardEngagementId, guardServiceToken, env, opts.fetchFn);
|
|
30890
|
+
}
|
|
30891
|
+
async function postAutonomy(act, engagementId, credential, env, fetchFn = globalThis.fetch) {
|
|
30892
|
+
const url2 = `${readControlOrigin(env).origin}/engagements/${encodeURIComponent(engagementId)}/autonomy/chat`;
|
|
30893
|
+
const controller = new AbortController();
|
|
30894
|
+
const timer = setTimeout(() => controller.abort(), CHAT_REQUEST_TIMEOUT_MS);
|
|
30895
|
+
try {
|
|
30896
|
+
const response = await fetchFn(url2, {
|
|
30897
|
+
method: "POST",
|
|
30898
|
+
headers: { Authorization: `Bearer ${credential}`, "Content-Type": "application/json" },
|
|
30899
|
+
body: JSON.stringify(autonomyChatRequestSchema.parse({ act })),
|
|
30900
|
+
signal: controller.signal
|
|
30901
|
+
});
|
|
30902
|
+
if (response.status === 401)
|
|
30903
|
+
return autonomyNotSignedInLine();
|
|
30904
|
+
if (response.status === 429 || response.status >= 500)
|
|
30905
|
+
return autonomyCannotReachLine();
|
|
30906
|
+
const parsed = autonomyChatResultSchema.safeParse(await response.json());
|
|
30907
|
+
if (!parsed.success)
|
|
30908
|
+
return autonomyCannotReachLine();
|
|
30909
|
+
if (parsed.data.outcome === "recorded" && response.status !== 200)
|
|
30910
|
+
return autonomyCannotReachLine();
|
|
30911
|
+
return autonomyLineForAnswer(parsed.data);
|
|
30912
|
+
} catch {
|
|
30913
|
+
return autonomyCannotReachLine();
|
|
30914
|
+
} finally {
|
|
30915
|
+
clearTimeout(timer);
|
|
30916
|
+
}
|
|
30917
|
+
}
|
|
29028
30918
|
async function decideReview(shape, opts, progress) {
|
|
29029
30919
|
const env = opts.env ?? process.env;
|
|
29030
30920
|
if (shape.kind === "phase") {
|
|
@@ -29206,18 +31096,36 @@ async function post(act, entry, request, engagementId, credential, env, fetchFn
|
|
|
29206
31096
|
}
|
|
29207
31097
|
}
|
|
29208
31098
|
|
|
31099
|
+
// dist/hooks/user-prompt-findings.js
|
|
31100
|
+
function runUserPromptFindings(opts) {
|
|
31101
|
+
try {
|
|
31102
|
+
const parsed = JSON.parse(opts.input);
|
|
31103
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
31104
|
+
return "";
|
|
31105
|
+
const sessionId = parsed["session_id"];
|
|
31106
|
+
const given = deliverFindings(sessionId, null);
|
|
31107
|
+
if (given.length === 0)
|
|
31108
|
+
return "";
|
|
31109
|
+
const project = realPathOf(projectFolder(opts.env ?? process.env));
|
|
31110
|
+
return `${userPromptContextReply(detailsBlock(given, project))}
|
|
31111
|
+
`;
|
|
31112
|
+
} catch {
|
|
31113
|
+
return "";
|
|
31114
|
+
}
|
|
31115
|
+
}
|
|
31116
|
+
|
|
29209
31117
|
// dist/resolve-engagement.js
|
|
29210
|
-
import { existsSync as
|
|
29211
|
-
import { join as
|
|
29212
|
-
import { homedir as
|
|
31118
|
+
import { existsSync as existsSync8, readFileSync as readFileSync24, realpathSync as realpathSync8 } from "node:fs";
|
|
31119
|
+
import { join as join18, sep as sep4 } from "node:path";
|
|
31120
|
+
import { homedir as homedir12 } from "node:os";
|
|
29213
31121
|
function defaultRegistryPath() {
|
|
29214
|
-
return
|
|
31122
|
+
return join18(homedir12(), ".halfcycle", "registry.json");
|
|
29215
31123
|
}
|
|
29216
31124
|
function readRegistry(registryPath = defaultRegistryPath()) {
|
|
29217
|
-
if (!
|
|
31125
|
+
if (!existsSync8(registryPath))
|
|
29218
31126
|
return {};
|
|
29219
31127
|
try {
|
|
29220
|
-
const parsed = JSON.parse(
|
|
31128
|
+
const parsed = JSON.parse(readFileSync24(registryPath, "utf-8"));
|
|
29221
31129
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
29222
31130
|
return {};
|
|
29223
31131
|
const out = {};
|
|
@@ -29232,7 +31140,7 @@ function readRegistry(registryPath = defaultRegistryPath()) {
|
|
|
29232
31140
|
}
|
|
29233
31141
|
function canonicalise(p) {
|
|
29234
31142
|
try {
|
|
29235
|
-
return
|
|
31143
|
+
return realpathSync8(p);
|
|
29236
31144
|
} catch {
|
|
29237
31145
|
return p;
|
|
29238
31146
|
}
|
|
@@ -29253,7 +31161,7 @@ function resolveEngagement(cwd, registry2) {
|
|
|
29253
31161
|
function isSelfOrAncestor(ancestor, target) {
|
|
29254
31162
|
if (ancestor === target)
|
|
29255
31163
|
return true;
|
|
29256
|
-
const withSep = ancestor.endsWith(
|
|
31164
|
+
const withSep = ancestor.endsWith(sep4) ? ancestor : ancestor + sep4;
|
|
29257
31165
|
return target.startsWith(withSep);
|
|
29258
31166
|
}
|
|
29259
31167
|
function runResolveEngagement(cwd, registryPath) {
|
|
@@ -29265,13 +31173,13 @@ function runResolveEngagement(cwd, registryPath) {
|
|
|
29265
31173
|
}
|
|
29266
31174
|
|
|
29267
31175
|
// dist/refresh-credential.js
|
|
29268
|
-
import { readFileSync as
|
|
29269
|
-
import { resolve as
|
|
31176
|
+
import { readFileSync as readFileSync26 } from "node:fs";
|
|
31177
|
+
import { resolve as resolve7 } from "node:path";
|
|
29270
31178
|
|
|
29271
31179
|
// dist/mcp-headers.js
|
|
29272
|
-
import { readFileSync as
|
|
29273
|
-
import { homedir as
|
|
29274
|
-
import { dirname as
|
|
31180
|
+
import { readFileSync as readFileSync25 } from "node:fs";
|
|
31181
|
+
import { homedir as homedir13 } from "node:os";
|
|
31182
|
+
import { dirname as dirname9, resolve as resolve6 } from "node:path";
|
|
29275
31183
|
function defaultWriteErr(text) {
|
|
29276
31184
|
process.stderr.write(text);
|
|
29277
31185
|
}
|
|
@@ -29284,12 +31192,12 @@ function headerLine(bearer, clientVersion) {
|
|
|
29284
31192
|
}
|
|
29285
31193
|
function projectRootFromBinary(binaryPath) {
|
|
29286
31194
|
const depth = VENDORED_BIN_REL.split("/").length - 1;
|
|
29287
|
-
return
|
|
31195
|
+
return resolve6(dirname9(binaryPath), ...Array(depth).fill(".."));
|
|
29288
31196
|
}
|
|
29289
31197
|
function readPinnedVersion(root) {
|
|
29290
31198
|
let raw;
|
|
29291
31199
|
try {
|
|
29292
|
-
raw =
|
|
31200
|
+
raw = readFileSync25(resolve6(root, BUNDLE_PIN_REL), "utf-8");
|
|
29293
31201
|
} catch {
|
|
29294
31202
|
return void 0;
|
|
29295
31203
|
}
|
|
@@ -29331,7 +31239,7 @@ function sentences(lines2) {
|
|
|
29331
31239
|
}
|
|
29332
31240
|
function badIdSentence(root) {
|
|
29333
31241
|
return sentences([
|
|
29334
|
-
`${
|
|
31242
|
+
`${resolve6(root, BUNDLE_PIN_REL)} names an engagement id that is not a Halfcycle project id,`,
|
|
29335
31243
|
'so no credential was read. Delete that file and run "npx halfcycle" again: this repository',
|
|
29336
31244
|
"is then set up as a new Halfcycle project."
|
|
29337
31245
|
]);
|
|
@@ -29345,7 +31253,7 @@ function storeProblemSentence(problem, root, home, engagementId) {
|
|
|
29345
31253
|
]);
|
|
29346
31254
|
case "no-id":
|
|
29347
31255
|
return sentences([
|
|
29348
|
-
`${
|
|
31256
|
+
`${resolve6(root, BUNDLE_PIN_REL)} names no engagement id.`,
|
|
29349
31257
|
'run "npx halfcycle" here to rewrite it.'
|
|
29350
31258
|
]);
|
|
29351
31259
|
case "bad-id":
|
|
@@ -29362,7 +31270,7 @@ function storeProblemSentence(problem, root, home, engagementId) {
|
|
|
29362
31270
|
case "no-credential":
|
|
29363
31271
|
return sentences([
|
|
29364
31272
|
`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 ${
|
|
31273
|
+
`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
31274
|
]);
|
|
29367
31275
|
}
|
|
29368
31276
|
}
|
|
@@ -29385,7 +31293,7 @@ async function refreshWithinDeadline(source, opts, writeErr) {
|
|
|
29385
31293
|
}
|
|
29386
31294
|
async function chooseBearer(opts) {
|
|
29387
31295
|
const writeErr = opts.writeErr ?? defaultWriteErr;
|
|
29388
|
-
const home = opts.home ??
|
|
31296
|
+
const home = opts.home ?? homedir13();
|
|
29389
31297
|
const root = projectRootFromBinary(opts.binaryPath);
|
|
29390
31298
|
const environmentToken = opts.environmentTokenFirst ? readEnvironmentToken() : void 0;
|
|
29391
31299
|
const store = resolveStore(root, home);
|
|
@@ -29512,10 +31420,11 @@ async function refreshCredentialToken(opts) {
|
|
|
29512
31420
|
var CHECKED_SOURCES = /* @__PURE__ */ new Set(["startup", "resume"]);
|
|
29513
31421
|
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
31422
|
var INITIALIZE_BODY = JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: {} });
|
|
31423
|
+
var SESSION_START_PROBE_ATTEMPTS = 2;
|
|
29515
31424
|
function registeredMcpUrl(projectRoot) {
|
|
29516
31425
|
let parsed;
|
|
29517
31426
|
try {
|
|
29518
|
-
parsed = JSON.parse(
|
|
31427
|
+
parsed = JSON.parse(readFileSync26(resolve7(projectRoot, MCP_REGISTRATION_REL), "utf-8"));
|
|
29519
31428
|
} catch {
|
|
29520
31429
|
return void 0;
|
|
29521
31430
|
}
|
|
@@ -29581,11 +31490,11 @@ async function postInitialize(url2, bearer, fetchFn, signal) {
|
|
|
29581
31490
|
}
|
|
29582
31491
|
return namesItsServer(body) ? void 0 : SERVICE_PROBLEM_SENTENCE;
|
|
29583
31492
|
}
|
|
29584
|
-
async function probeDelivery(url2, bearer, fetchFn, deadlineMs) {
|
|
31493
|
+
async function probeDelivery(url2, bearer, fetchFn, deadlineMs, maxAttempts) {
|
|
29585
31494
|
const controller = new AbortController();
|
|
29586
31495
|
const timer = setTimeout(() => controller.abort(), deadlineMs);
|
|
29587
31496
|
try {
|
|
29588
|
-
for (let attempt2 = 0; attempt2 <
|
|
31497
|
+
for (let attempt2 = 0; attempt2 < maxAttempts; attempt2++) {
|
|
29589
31498
|
const outcome = await postInitialize(url2, bearer, fetchFn, controller.signal);
|
|
29590
31499
|
if (outcome !== CUT_OFF)
|
|
29591
31500
|
return outcome;
|
|
@@ -29611,7 +31520,7 @@ async function checkConnection(renewedToken, opts) {
|
|
|
29611
31520
|
if (CREDENTIAL_PLACEHOLDERS.includes(bearer)) {
|
|
29612
31521
|
return { sentence: PLACEHOLDER_SENTENCES[bearer], url: url2, bearer };
|
|
29613
31522
|
}
|
|
29614
|
-
const outcome = await probeDelivery(url2, bearer, opts.fetchFn ?? globalThis.fetch, opts.checkDeadlineMs ?? CONNECTION_CHECK_DEADLINE_MS);
|
|
31523
|
+
const outcome = await probeDelivery(url2, bearer, opts.fetchFn ?? globalThis.fetch, opts.checkDeadlineMs ?? CONNECTION_CHECK_DEADLINE_MS, opts.maxAttempts ?? SESSION_START_PROBE_ATTEMPTS);
|
|
29615
31524
|
return { sentence: outcome ?? CONNECTED_SENTENCE, url: url2, bearer };
|
|
29616
31525
|
}
|
|
29617
31526
|
function positionRequestBody(engagementId) {
|
|
@@ -29722,6 +31631,20 @@ async function runRefreshCredential(opts) {
|
|
|
29722
31631
|
}
|
|
29723
31632
|
}
|
|
29724
31633
|
}
|
|
31634
|
+
if (CHECKED_SOURCES.has(input["source"])) {
|
|
31635
|
+
const config2 = readConfig().config;
|
|
31636
|
+
if (config2 !== null) {
|
|
31637
|
+
const remainingForMargin = SESSION_START_HOOK_TIMEOUT_MS - SESSION_START_MARGIN_MS - (Date.now() - startedAt);
|
|
31638
|
+
await cacheAutonomySetting({
|
|
31639
|
+
controlOrigin: readControlOrigin().origin,
|
|
31640
|
+
engagementId: config2.guardEngagementId,
|
|
31641
|
+
bearer: renewedToken ?? config2.guardServiceToken,
|
|
31642
|
+
fetchFn: opts.fetchFn ?? globalThis.fetch,
|
|
31643
|
+
home: opts.home,
|
|
31644
|
+
deadlineMs: Math.min(opts.autonomyStateDeadlineMs ?? AUTONOMY_STATE_DEADLINE_MS, remainingForMargin)
|
|
31645
|
+
});
|
|
31646
|
+
}
|
|
31647
|
+
}
|
|
29725
31648
|
if (readConfig().config === null) {
|
|
29726
31649
|
const remedySaid = parts.some(namesTheSignInCommand);
|
|
29727
31650
|
parts.push(noCoverageStatement(readNeverConfiguredReason(), remedySaid, pinnedEngagementId(projectRootFromBinary(opts.binaryPath))));
|
|
@@ -29748,8 +31671,8 @@ async function runRefreshCredential(opts) {
|
|
|
29748
31671
|
import { fileURLToPath } from "node:url";
|
|
29749
31672
|
|
|
29750
31673
|
// dist/capture.js
|
|
29751
|
-
import { readFileSync as
|
|
29752
|
-
import { dirname as
|
|
31674
|
+
import { readFileSync as readFileSync27, writeFileSync as writeFileSync15, mkdirSync as mkdirSync10, existsSync as existsSync9 } from "node:fs";
|
|
31675
|
+
import { dirname as dirname10 } from "node:path";
|
|
29753
31676
|
function runCapture(provider, endpoint, opts = {}) {
|
|
29754
31677
|
const repoRoot = opts.repoRoot ?? process.cwd();
|
|
29755
31678
|
const raw = opts.input ?? readInputFile(opts.fromFile);
|
|
@@ -29784,8 +31707,8 @@ function runCapture(provider, endpoint, opts = {}) {
|
|
|
29784
31707
|
const fixture = fixturePath(repoRoot, provider, slug);
|
|
29785
31708
|
const manifestFile = manifestPath(repoRoot);
|
|
29786
31709
|
try {
|
|
29787
|
-
|
|
29788
|
-
|
|
31710
|
+
mkdirSync10(dirname10(fixture), { recursive: true });
|
|
31711
|
+
writeFileSync15(fixture, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
29789
31712
|
} catch (err) {
|
|
29790
31713
|
const msg = err instanceof Error ? err.message : String(err);
|
|
29791
31714
|
return { ok: false, kind: "write-failed", message: `could not write fixture: ${msg}` };
|
|
@@ -29816,7 +31739,7 @@ function readInputFile(fromFile) {
|
|
|
29816
31739
|
if (!fromFile)
|
|
29817
31740
|
return void 0;
|
|
29818
31741
|
try {
|
|
29819
|
-
return
|
|
31742
|
+
return readFileSync27(fromFile, "utf-8");
|
|
29820
31743
|
} catch {
|
|
29821
31744
|
return void 0;
|
|
29822
31745
|
}
|
|
@@ -29827,11 +31750,11 @@ function relativeFixturePath(repoRoot, fixture) {
|
|
|
29827
31750
|
}
|
|
29828
31751
|
|
|
29829
31752
|
// 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
|
|
31753
|
+
import { spawn as spawn2, spawnSync as spawnSync3 } from "node:child_process";
|
|
31754
|
+
import { createHash as createHash5, randomUUID as randomUUID3 } from "node:crypto";
|
|
31755
|
+
import { createWriteStream, mkdirSync as mkdirSync11, readdirSync as readdirSync10, readFileSync as readFileSync28, renameSync as renameSync13, unlinkSync as unlinkSync11, writeFileSync as writeFileSync16 } from "node:fs";
|
|
31756
|
+
import { homedir as homedir14 } from "node:os";
|
|
31757
|
+
import { dirname as dirname11, join as join19 } from "node:path";
|
|
29835
31758
|
|
|
29836
31759
|
// dist/test-counts.js
|
|
29837
31760
|
function sum(values) {
|
|
@@ -29866,36 +31789,36 @@ function jestCounts(output) {
|
|
|
29866
31789
|
if (lines2.length === 0)
|
|
29867
31790
|
return null;
|
|
29868
31791
|
let passed = 0;
|
|
29869
|
-
let
|
|
31792
|
+
let failed2 = 0;
|
|
29870
31793
|
for (const line2 of lines2) {
|
|
29871
31794
|
passed += firstNumber(line2[1], /(\d+) passed/) ?? 0;
|
|
29872
|
-
|
|
31795
|
+
failed2 += firstNumber(line2[1], /(\d+) failed/) ?? 0;
|
|
29873
31796
|
}
|
|
29874
|
-
return { passed, failed };
|
|
31797
|
+
return { passed, failed: failed2 };
|
|
29875
31798
|
}
|
|
29876
31799
|
function vitestCounts(output) {
|
|
29877
31800
|
const lines2 = [...output.matchAll(/^\s*Tests\s+(.+)$/gm)];
|
|
29878
31801
|
if (lines2.length === 0)
|
|
29879
31802
|
return null;
|
|
29880
31803
|
let passed = 0;
|
|
29881
|
-
let
|
|
31804
|
+
let failed2 = 0;
|
|
29882
31805
|
for (const line2 of lines2) {
|
|
29883
31806
|
passed += firstNumber(line2[1], /(\d+) passed/) ?? 0;
|
|
29884
|
-
|
|
31807
|
+
failed2 += firstNumber(line2[1], /(\d+) failed/) ?? 0;
|
|
29885
31808
|
}
|
|
29886
|
-
return { passed, failed };
|
|
31809
|
+
return { passed, failed: failed2 };
|
|
29887
31810
|
}
|
|
29888
31811
|
function pytestCounts(output) {
|
|
29889
31812
|
const lines2 = [...output.matchAll(/^\W*((?:\d+ \w+(?:, )?)+)\s+in [\d.]+s\W*$/gm)];
|
|
29890
31813
|
if (lines2.length === 0)
|
|
29891
31814
|
return null;
|
|
29892
31815
|
let passed = 0;
|
|
29893
|
-
let
|
|
31816
|
+
let failed2 = 0;
|
|
29894
31817
|
for (const line2 of lines2) {
|
|
29895
31818
|
passed += firstNumber(line2[1], /(\d+) passed/) ?? 0;
|
|
29896
|
-
|
|
31819
|
+
failed2 += firstNumber(line2[1], /(\d+) failed/) ?? 0;
|
|
29897
31820
|
}
|
|
29898
|
-
return { passed, failed };
|
|
31821
|
+
return { passed, failed: failed2 };
|
|
29899
31822
|
}
|
|
29900
31823
|
function cargoTestCounts(output) {
|
|
29901
31824
|
const lines2 = [...output.matchAll(/^test result: (?:ok|FAILED)\.\s*(\d+) passed;\s*(\d+) failed;/gm)];
|
|
@@ -29940,19 +31863,19 @@ function isProofTestPath(path) {
|
|
|
29940
31863
|
function testRunsDir(engagementId, home) {
|
|
29941
31864
|
if (!isEngagementId(engagementId))
|
|
29942
31865
|
return null;
|
|
29943
|
-
return
|
|
31866
|
+
return join19(home ?? homedir14(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, TEST_RUNS_DIR_NAME);
|
|
29944
31867
|
}
|
|
29945
31868
|
function commandIdOf(command) {
|
|
29946
|
-
return
|
|
31869
|
+
return createHash5("sha256").update(command, "utf8").digest("hex").slice(0, 16);
|
|
29947
31870
|
}
|
|
29948
31871
|
var PLAIN_WORD = /^[A-Za-z0-9_./:=@%+,-]+$/;
|
|
29949
|
-
function commandFromWords(words,
|
|
31872
|
+
function commandFromWords(words, platform4 = process.platform) {
|
|
29950
31873
|
if (words.length === 1)
|
|
29951
31874
|
return words[0];
|
|
29952
31875
|
return words.map((word) => {
|
|
29953
31876
|
if (PLAIN_WORD.test(word))
|
|
29954
31877
|
return word;
|
|
29955
|
-
return
|
|
31878
|
+
return platform4 === "win32" ? `"${word.replace(/"/g, '""')}"` : quoteForShell(word);
|
|
29956
31879
|
}).join(" ");
|
|
29957
31880
|
}
|
|
29958
31881
|
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 +31949,21 @@ function parseProveArgs(args2) {
|
|
|
30026
31949
|
};
|
|
30027
31950
|
}
|
|
30028
31951
|
function writeJsonFile2(path, value) {
|
|
30029
|
-
|
|
30030
|
-
const tmp = `${path}.${process.pid}.${
|
|
30031
|
-
|
|
30032
|
-
|
|
31952
|
+
mkdirSync11(dirname11(path), { recursive: true, mode: 448 });
|
|
31953
|
+
const tmp = `${path}.${process.pid}.${randomUUID3()}.tmp`;
|
|
31954
|
+
writeFileSync16(tmp, JSON.stringify(value), { mode: 384 });
|
|
31955
|
+
renameSync13(tmp, path);
|
|
30033
31956
|
}
|
|
30034
31957
|
function subjectKey(subject) {
|
|
30035
31958
|
return subject.kind === "task" ? ["task", subject.phase, subject.task] : ["change", subject.change];
|
|
30036
31959
|
}
|
|
30037
31960
|
function keptCheckPath(dir, subject, label) {
|
|
30038
|
-
const key =
|
|
30039
|
-
return
|
|
31961
|
+
const key = createHash5("sha256").update(JSON.stringify([...subjectKey(subject), label]), "utf8").digest("hex");
|
|
31962
|
+
return join19(dir, COMMANDS_DIR_NAME, `${key.slice(0, 32)}.json`);
|
|
30040
31963
|
}
|
|
30041
31964
|
function readKeptCheck(dir, subject, label) {
|
|
30042
31965
|
try {
|
|
30043
|
-
const raw = JSON.parse(
|
|
31966
|
+
const raw = JSON.parse(readFileSync28(keptCheckPath(dir, subject, label), "utf-8"));
|
|
30044
31967
|
if (typeof raw.command !== "string" || raw.command === "" || typeof raw.commandId !== "string")
|
|
30045
31968
|
return null;
|
|
30046
31969
|
const b = raw.before;
|
|
@@ -30051,21 +31974,21 @@ function readKeptCheck(dir, subject, label) {
|
|
|
30051
31974
|
}
|
|
30052
31975
|
}
|
|
30053
31976
|
function queuePath(dir, runId) {
|
|
30054
|
-
return
|
|
31977
|
+
return join19(dir, QUEUE_DIR_NAME, `${runId}.json`);
|
|
30055
31978
|
}
|
|
30056
31979
|
function readQueue(dir) {
|
|
30057
31980
|
let names;
|
|
30058
31981
|
try {
|
|
30059
|
-
names =
|
|
31982
|
+
names = readdirSync10(join19(dir, QUEUE_DIR_NAME)).filter((n) => n.endsWith(".json"));
|
|
30060
31983
|
} catch {
|
|
30061
31984
|
return [];
|
|
30062
31985
|
}
|
|
30063
31986
|
const reports = [];
|
|
30064
31987
|
for (const name of names) {
|
|
30065
|
-
const path =
|
|
31988
|
+
const path = join19(dir, QUEUE_DIR_NAME, name);
|
|
30066
31989
|
let parsed = null;
|
|
30067
31990
|
try {
|
|
30068
|
-
parsed = testRunReportSchema.safeParse(JSON.parse(
|
|
31991
|
+
parsed = testRunReportSchema.safeParse(JSON.parse(readFileSync28(path, "utf-8")));
|
|
30069
31992
|
} catch {
|
|
30070
31993
|
parsed = null;
|
|
30071
31994
|
}
|
|
@@ -30078,7 +32001,7 @@ function readQueue(dir) {
|
|
|
30078
32001
|
}
|
|
30079
32002
|
function removeFile(path) {
|
|
30080
32003
|
try {
|
|
30081
|
-
|
|
32004
|
+
unlinkSync11(path);
|
|
30082
32005
|
} catch {
|
|
30083
32006
|
}
|
|
30084
32007
|
}
|
|
@@ -30118,13 +32041,13 @@ async function sendQueuedRuns(dir, config2, controlOrigin, fetchFn) {
|
|
|
30118
32041
|
}
|
|
30119
32042
|
return outcomes;
|
|
30120
32043
|
}
|
|
30121
|
-
function killTree(child,
|
|
32044
|
+
function killTree(child, platform4, signal) {
|
|
30122
32045
|
const pid = child.pid;
|
|
30123
32046
|
if (pid === void 0)
|
|
30124
32047
|
return;
|
|
30125
32048
|
try {
|
|
30126
|
-
if (
|
|
30127
|
-
|
|
32049
|
+
if (platform4 === "win32") {
|
|
32050
|
+
spawnSync3("taskkill", ["/pid", String(pid), "/T", "/F"], { windowsHide: true });
|
|
30128
32051
|
} else {
|
|
30129
32052
|
process.kill(-pid, signal);
|
|
30130
32053
|
}
|
|
@@ -30136,16 +32059,16 @@ function killTree(child, platform3, signal) {
|
|
|
30136
32059
|
}
|
|
30137
32060
|
}
|
|
30138
32061
|
function runCommand(command, repoRoot, logPath, env, deps) {
|
|
30139
|
-
const
|
|
32062
|
+
const platform4 = deps.platform ?? process.platform;
|
|
30140
32063
|
const out = deps.stdout ?? process.stdout;
|
|
30141
32064
|
const err = deps.stderr ?? process.stderr;
|
|
30142
32065
|
const deadline = deps.deadlineMs ?? PROVE_RUN_DEADLINE_MS;
|
|
30143
32066
|
const childEnv = { ...env };
|
|
30144
32067
|
delete childEnv[SERVICE_TOKEN_VAR];
|
|
30145
|
-
|
|
32068
|
+
mkdirSync11(dirname11(logPath), { recursive: true, mode: 448 });
|
|
30146
32069
|
const log = createWriteStream(logPath, { mode: 384 });
|
|
30147
32070
|
const began = process.hrtime.bigint();
|
|
30148
|
-
return new Promise((
|
|
32071
|
+
return new Promise((resolve8) => {
|
|
30149
32072
|
let timedOut = false;
|
|
30150
32073
|
let started = true;
|
|
30151
32074
|
let settled = false;
|
|
@@ -30154,15 +32077,15 @@ function runCommand(command, repoRoot, logPath, env, deps) {
|
|
|
30154
32077
|
env: childEnv,
|
|
30155
32078
|
shell: true,
|
|
30156
32079
|
stdio: ["ignore", "pipe", "pipe"],
|
|
30157
|
-
detached:
|
|
32080
|
+
detached: platform4 !== "win32",
|
|
30158
32081
|
windowsHide: true
|
|
30159
32082
|
});
|
|
30160
|
-
const onSignal = (signal) => killTree(child,
|
|
32083
|
+
const onSignal = (signal) => killTree(child, platform4, signal);
|
|
30161
32084
|
process.on("SIGINT", onSignal);
|
|
30162
32085
|
process.on("SIGTERM", onSignal);
|
|
30163
32086
|
const timer = setTimeout(() => {
|
|
30164
32087
|
timedOut = true;
|
|
30165
|
-
killTree(child,
|
|
32088
|
+
killTree(child, platform4, "SIGKILL");
|
|
30166
32089
|
}, deadline);
|
|
30167
32090
|
child.stdout?.on("data", (chunk) => {
|
|
30168
32091
|
out.write(chunk);
|
|
@@ -30180,7 +32103,7 @@ function runCommand(command, repoRoot, logPath, env, deps) {
|
|
|
30180
32103
|
process.off("SIGINT", onSignal);
|
|
30181
32104
|
process.off("SIGTERM", onSignal);
|
|
30182
32105
|
const durationMs = Number((process.hrtime.bigint() - began) / 1000000n);
|
|
30183
|
-
log.end(() =>
|
|
32106
|
+
log.end(() => resolve8({ exitCode: timedOut || !started ? null : code, timedOut, started, durationMs }));
|
|
30184
32107
|
};
|
|
30185
32108
|
child.on("error", (e) => {
|
|
30186
32109
|
started = false;
|
|
@@ -30301,14 +32224,14 @@ ${PROVE_USAGE}`);
|
|
|
30301
32224
|
}
|
|
30302
32225
|
if (request.when === "before" && changedOutsideTests > 0)
|
|
30303
32226
|
say(lateBeforeLine(changedOutsideTests));
|
|
30304
|
-
const runId =
|
|
30305
|
-
const logPath =
|
|
32227
|
+
const runId = randomUUID3();
|
|
32228
|
+
const logPath = join19(dir, `${runId}.log`);
|
|
30306
32229
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
30307
32230
|
const deadline = deps.deadlineMs ?? PROVE_RUN_DEADLINE_MS;
|
|
30308
32231
|
const end = await runCommand(command, repoRoot, logPath, env, deps);
|
|
30309
32232
|
let output = "";
|
|
30310
32233
|
try {
|
|
30311
|
-
output =
|
|
32234
|
+
output = readFileSync28(logPath, "utf-8");
|
|
30312
32235
|
} catch {
|
|
30313
32236
|
output = "";
|
|
30314
32237
|
}
|
|
@@ -30358,7 +32281,7 @@ ${PROVE_USAGE}`);
|
|
|
30358
32281
|
|
|
30359
32282
|
// dist/statusline.js
|
|
30360
32283
|
import { spawn as spawn3 } from "node:child_process";
|
|
30361
|
-
import { readFileSync as
|
|
32284
|
+
import { readFileSync as readFileSync29 } from "node:fs";
|
|
30362
32285
|
import nodePath5 from "node:path";
|
|
30363
32286
|
var OWN_STATUS_LINE_DEADLINE_MS = 5e3;
|
|
30364
32287
|
var PERSON_SETTINGS_FILENAME = "settings.json";
|
|
@@ -30396,7 +32319,7 @@ function halfcycleStatusLineOf(figures) {
|
|
|
30396
32319
|
}
|
|
30397
32320
|
function personStatusLineCommand(configFolder) {
|
|
30398
32321
|
try {
|
|
30399
|
-
const v = JSON.parse(
|
|
32322
|
+
const v = JSON.parse(readFileSync29(nodePath5.join(configFolder, PERSON_SETTINGS_FILENAME), "utf-8"));
|
|
30400
32323
|
const line2 = v["statusLine"];
|
|
30401
32324
|
if (typeof line2 !== "object" || line2 === null)
|
|
30402
32325
|
return null;
|
|
@@ -30420,14 +32343,14 @@ function personCommandEnv(env) {
|
|
|
30420
32343
|
}
|
|
30421
32344
|
return out;
|
|
30422
32345
|
}
|
|
30423
|
-
var runOwnCommandReal = (command, input, env) => new Promise((
|
|
32346
|
+
var runOwnCommandReal = (command, input, env) => new Promise((resolve8) => {
|
|
30424
32347
|
let out = "";
|
|
30425
32348
|
let settled = false;
|
|
30426
32349
|
const done = (v) => {
|
|
30427
32350
|
if (settled)
|
|
30428
32351
|
return;
|
|
30429
32352
|
settled = true;
|
|
30430
|
-
|
|
32353
|
+
resolve8(v);
|
|
30431
32354
|
};
|
|
30432
32355
|
try {
|
|
30433
32356
|
const child = spawn3(command, {
|
|
@@ -30490,6 +32413,43 @@ async function runStatusline(raw, deps = {}) {
|
|
|
30490
32413
|
`;
|
|
30491
32414
|
}
|
|
30492
32415
|
|
|
32416
|
+
// dist/check-connection.js
|
|
32417
|
+
function connectionKind(bearer, sentence) {
|
|
32418
|
+
if (bearer === NOT_THIS_ACCOUNT_PLACEHOLDER)
|
|
32419
|
+
return "other-account";
|
|
32420
|
+
if (bearer === UPDATE_CLAUDE_CODE_PLACEHOLDER)
|
|
32421
|
+
return "update-claude-code";
|
|
32422
|
+
if (bearer === NO_CREDENTIAL_PLACEHOLDER)
|
|
32423
|
+
return "not-signed-in";
|
|
32424
|
+
if (sentence === CONNECTED_SENTENCE)
|
|
32425
|
+
return "connected";
|
|
32426
|
+
if (sentence === CANNOT_REACH_SENTENCE)
|
|
32427
|
+
return "cannot-reach";
|
|
32428
|
+
if (sentence === SERVICE_PROBLEM_SENTENCE)
|
|
32429
|
+
return "service-problem";
|
|
32430
|
+
if (sentence === NO_CREDENTIAL_SENTENCE)
|
|
32431
|
+
return "signed-out";
|
|
32432
|
+
throw new Error("the connection check gave an outcome with no kind");
|
|
32433
|
+
}
|
|
32434
|
+
async function checkConnectionLine(opts) {
|
|
32435
|
+
const result = await checkConnection(void 0, {
|
|
32436
|
+
...opts,
|
|
32437
|
+
maxAttempts: 1,
|
|
32438
|
+
writeErr: opts.writeErr ?? discardReason
|
|
32439
|
+
});
|
|
32440
|
+
if (result === void 0)
|
|
32441
|
+
return { kind: "not-registered", sentence: null };
|
|
32442
|
+
return { kind: connectionKind(result.bearer, result.sentence), sentence: result.sentence };
|
|
32443
|
+
}
|
|
32444
|
+
async function runCheckConnection(opts) {
|
|
32445
|
+
const line2 = await checkConnectionLine(opts);
|
|
32446
|
+
return `${JSON.stringify(line2)}
|
|
32447
|
+
`;
|
|
32448
|
+
}
|
|
32449
|
+
function discardReason() {
|
|
32450
|
+
return void 0;
|
|
32451
|
+
}
|
|
32452
|
+
|
|
30493
32453
|
// dist/bin.js
|
|
30494
32454
|
var args = process.argv.slice(2);
|
|
30495
32455
|
async function main() {
|
|
@@ -30572,6 +32532,18 @@ async function main() {
|
|
|
30572
32532
|
process.stdout.write(line2, () => process.exit(0));
|
|
30573
32533
|
return;
|
|
30574
32534
|
}
|
|
32535
|
+
if (cmd === "check-connection") {
|
|
32536
|
+
let line2;
|
|
32537
|
+
try {
|
|
32538
|
+
line2 = await runCheckConnection({ binaryPath: fileURLToPath(import.meta.url) });
|
|
32539
|
+
} catch {
|
|
32540
|
+
process.stderr.write("halfcycle-runner: the connection could not be checked.\n");
|
|
32541
|
+
process.exit(1);
|
|
32542
|
+
return;
|
|
32543
|
+
}
|
|
32544
|
+
process.stdout.write(line2, () => process.exit(0));
|
|
32545
|
+
return;
|
|
32546
|
+
}
|
|
30575
32547
|
if (cmd === "mcp-headers") {
|
|
30576
32548
|
const line2 = await mcpHeaders({ binaryPath: fileURLToPath(import.meta.url) });
|
|
30577
32549
|
process.stdout.write(line2, () => process.exit(0));
|
|
@@ -30650,8 +32622,18 @@ Commit the fixture; the next guard evaluation of this endpoint will pass.
|
|
|
30650
32622
|
process.stdout.write(reply, () => process.exit(0));
|
|
30651
32623
|
return;
|
|
30652
32624
|
}
|
|
32625
|
+
if (sub === "user-prompt-findings") {
|
|
32626
|
+
let reply = "";
|
|
32627
|
+
try {
|
|
32628
|
+
reply = runUserPromptFindings({ input: await readStdin2() });
|
|
32629
|
+
} catch {
|
|
32630
|
+
reply = "";
|
|
32631
|
+
}
|
|
32632
|
+
process.stdout.write(reply, () => process.exit(0));
|
|
32633
|
+
return;
|
|
32634
|
+
}
|
|
30653
32635
|
process.stderr.write(`halfcycle-runner: unknown hook "${sub ?? ""}"
|
|
30654
|
-
Valid hooks: post-tool-use, pre-tool-use, stop, subagent-stop, user-prompt-submit
|
|
32636
|
+
Valid hooks: post-tool-use, pre-tool-use, stop, subagent-stop, user-prompt-submit, user-prompt-findings
|
|
30655
32637
|
`);
|
|
30656
32638
|
process.exit(1);
|
|
30657
32639
|
return;
|
|
@@ -30663,7 +32645,9 @@ Commit the fixture; the next guard evaluation of this endpoint will pass.
|
|
|
30663
32645
|
halfcycle-runner hook stop
|
|
30664
32646
|
halfcycle-runner hook subagent-stop
|
|
30665
32647
|
halfcycle-runner hook user-prompt-submit
|
|
32648
|
+
halfcycle-runner hook user-prompt-findings
|
|
30666
32649
|
halfcycle-runner refresh-credential
|
|
32650
|
+
halfcycle-runner check-connection
|
|
30667
32651
|
halfcycle-runner mcp-headers
|
|
30668
32652
|
halfcycle-runner observe
|
|
30669
32653
|
halfcycle-runner observe --change <sortingId>
|
|
@@ -30678,10 +32662,10 @@ Commit the fixture; the next guard evaluation of this endpoint will pass.
|
|
|
30678
32662
|
process.exit(1);
|
|
30679
32663
|
}
|
|
30680
32664
|
function readStdin3() {
|
|
30681
|
-
return new Promise((
|
|
32665
|
+
return new Promise((resolve8, reject) => {
|
|
30682
32666
|
const chunks = [];
|
|
30683
32667
|
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
30684
|
-
process.stdin.on("end", () =>
|
|
32668
|
+
process.stdin.on("end", () => resolve8(Buffer.concat(chunks).toString("utf-8")));
|
|
30685
32669
|
process.stdin.on("error", reject);
|
|
30686
32670
|
});
|
|
30687
32671
|
}
|