terminalhire 0.30.1 → 0.32.0

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.
@@ -45,9 +45,9 @@ var init_keytar = __esm({
45
45
  }
46
46
  });
47
47
 
48
- // node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node
48
+ // node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/agent-a0bc554500876cd51/node_modules/keytar/build/Release/keytar.node
49
49
  var require_keytar = __commonJS({
50
- "node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node"(exports, module) {
50
+ "node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/agent-a0bc554500876cd51/node_modules/keytar/build/Release/keytar.node"(exports, module) {
51
51
  "use strict";
52
52
  init_keytar();
53
53
  try {
@@ -8062,9 +8062,9 @@ var init_keytar = __esm({
8062
8062
  }
8063
8063
  });
8064
8064
 
8065
- // node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node
8065
+ // node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/agent-a0bc554500876cd51/node_modules/keytar/build/Release/keytar.node
8066
8066
  var require_keytar = __commonJS({
8067
- "node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node"(exports, module) {
8067
+ "node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/agent-a0bc554500876cd51/node_modules/keytar/build/Release/keytar.node"(exports, module) {
8068
8068
  "use strict";
8069
8069
  init_keytar();
8070
8070
  try {
@@ -4043,9 +4043,9 @@ var init_keytar = __esm({
4043
4043
  }
4044
4044
  });
4045
4045
 
4046
- // node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node
4046
+ // node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/agent-a0bc554500876cd51/node_modules/keytar/build/Release/keytar.node
4047
4047
  var require_keytar = __commonJS({
4048
- "node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node"(exports, module) {
4048
+ "node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/agent-a0bc554500876cd51/node_modules/keytar/build/Release/keytar.node"(exports, module) {
4049
4049
  "use strict";
4050
4050
  init_keytar();
4051
4051
  try {
@@ -4071,9 +4071,9 @@ var init_keytar = __esm({
4071
4071
  }
4072
4072
  });
4073
4073
 
4074
- // node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node
4074
+ // node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/agent-a0bc554500876cd51/node_modules/keytar/build/Release/keytar.node
4075
4075
  var require_keytar = __commonJS({
4076
- "node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node"(exports, module) {
4076
+ "node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/agent-a0bc554500876cd51/node_modules/keytar/build/Release/keytar.node"(exports, module) {
4077
4077
  "use strict";
4078
4078
  init_keytar();
4079
4079
  try {
@@ -8185,9 +8185,9 @@ var init_keytar = __esm({
8185
8185
  }
8186
8186
  });
8187
8187
 
8188
- // node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node
8188
+ // node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/agent-a0bc554500876cd51/node_modules/keytar/build/Release/keytar.node
8189
8189
  var require_keytar = __commonJS({
8190
- "node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node"(exports, module) {
8190
+ "node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/agent-a0bc554500876cd51/node_modules/keytar/build/Release/keytar.node"(exports, module) {
8191
8191
  "use strict";
8192
8192
  init_keytar();
8193
8193
  try {
@@ -8239,6 +8239,8 @@ var require_keytar2 = __commonJS({
8239
8239
  // src/repo-policy.ts
8240
8240
  var repo_policy_exports = {};
8241
8241
  __export(repo_policy_exports, {
8242
+ POLICY_RULESET_VERSION: () => POLICY_RULESET_VERSION,
8243
+ auditContent: () => auditContent,
8242
8244
  checkRepoPolicy: () => checkRepoPolicy
8243
8245
  });
8244
8246
  async function fetchContentsFile(fetchImpl, repoFullName, path) {
@@ -8260,23 +8262,50 @@ async function fetchContentsFile(fetchImpl, repoFullName, path) {
8260
8262
  return { ok: false, missing: false, content: null };
8261
8263
  }
8262
8264
  }
8263
- function findHits(file, content) {
8264
- const lines = content.split("\n");
8265
+ function classifyLine(line) {
8266
+ if (PROHIBITED_PATTERNS.some((re) => re.test(line))) return "prohibited";
8267
+ if (DISCLOSURE_PATTERNS.some((re) => re.test(line))) return "disclosure-required";
8268
+ if (AI_SIGNAL_PATTERNS.some((p) => p.re.test(line))) return "ai-mentioned";
8269
+ return null;
8270
+ }
8271
+ function sanitizeExcerpt(text) {
8272
+ return text.replace(/[\x00-\x08\x0B-\x1F\x7F-\x9F]/g, "");
8273
+ }
8274
+ function excerptAround(lines, i) {
8275
+ const start = Math.max(0, i - 2);
8276
+ const end = Math.min(lines.length, i + 3);
8277
+ return sanitizeExcerpt(lines.slice(start, end).join("\n"));
8278
+ }
8279
+ function auditContent(files) {
8265
8280
  const hits = [];
8266
- for (let i = 0; i < lines.length; i++) {
8267
- const line = lines[i];
8268
- if (!AI_SIGNAL_PATTERNS.some((p) => p.re.test(line))) continue;
8269
- const start = Math.max(0, i - 2);
8270
- const end = Math.min(lines.length, i + 3);
8271
- hits.push({ file, excerpt: lines.slice(start, end).join("\n") });
8281
+ const requirements = [];
8282
+ const seenKinds = /* @__PURE__ */ new Set();
8283
+ for (const { file, content } of files) {
8284
+ const lines = content.split("\n");
8285
+ for (let i = 0; i < lines.length; i++) {
8286
+ const rule = classifyLine(lines[i]);
8287
+ if (rule) hits.push({ file, excerpt: excerptAround(lines, i), rule });
8288
+ for (const { kind, re } of REQUIREMENT_PATTERNS) {
8289
+ if (seenKinds.has(kind) || !re.test(lines[i])) continue;
8290
+ seenKinds.add(kind);
8291
+ requirements.push({ kind, file, excerpt: excerptAround(lines, i) });
8292
+ }
8293
+ }
8272
8294
  }
8273
- return hits;
8295
+ let verdict = "clean";
8296
+ for (const h of hits) {
8297
+ if (verdict === "clean" || VERDICT_SEVERITY[h.rule] > VERDICT_SEVERITY[verdict]) {
8298
+ verdict = h.rule;
8299
+ }
8300
+ }
8301
+ const assignment = seenKinds.has("take-bot") ? "take-bot" : seenKinds.has("assignment-required") ? "required" : "none";
8302
+ return { hits, requirements, verdict, assignment };
8274
8303
  }
8275
8304
  async function checkRepoPolicy(repoFullName, opts = {}) {
8276
8305
  const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
8277
8306
  let requestsUsed = 0;
8278
8307
  let hadError = false;
8279
- const hits = [];
8308
+ const files = [];
8280
8309
  outer: for (const group of CANDIDATE_GROUPS) {
8281
8310
  for (const path of group) {
8282
8311
  if (requestsUsed >= MAX_REQUESTS) break outer;
@@ -8287,21 +8316,27 @@ async function checkRepoPolicy(repoFullName, opts = {}) {
8287
8316
  continue;
8288
8317
  }
8289
8318
  if (outcome.missing) continue;
8290
- if (outcome.content) hits.push(...findHits(path, outcome.content));
8319
+ if (outcome.content) files.push({ file: path, content: outcome.content });
8291
8320
  break;
8292
8321
  }
8293
8322
  }
8294
- if (hits.length > 0) return { status: "flagged", hits };
8295
- if (hadError) return { status: "unavailable", hits: [] };
8296
- return { status: "clean", hits: [] };
8323
+ const { hits, requirements, verdict, assignment } = auditContent(files);
8324
+ if (hits.length > 0) {
8325
+ return { status: "flagged", verdict, hits, requirements, assignment, rulesetVersion: POLICY_RULESET_VERSION };
8326
+ }
8327
+ if (hadError) {
8328
+ return { status: "unavailable", verdict: "unavailable", hits: [], requirements, assignment, rulesetVersion: POLICY_RULESET_VERSION };
8329
+ }
8330
+ return { status: "clean", verdict: "clean", hits: [], requirements, assignment, rulesetVersion: POLICY_RULESET_VERSION };
8297
8331
  }
8298
- var GH_API, GH_HEADERS, MAX_REQUESTS, AI_SIGNAL_PATTERNS, CANDIDATE_GROUPS;
8332
+ var GH_API, GH_HEADERS, MAX_REQUESTS, POLICY_RULESET_VERSION, AI_SIGNAL_PATTERNS, AI_TERM, PROHIBITED_PATTERNS, DISCLOSURE_PATTERNS, REQUIREMENT_PATTERNS, CANDIDATE_GROUPS, VERDICT_SEVERITY;
8299
8333
  var init_repo_policy = __esm({
8300
8334
  "src/repo-policy.ts"() {
8301
8335
  "use strict";
8302
8336
  GH_API = "https://api.github.com";
8303
8337
  GH_HEADERS = { "User-Agent": "terminalhire-claim", Accept: "application/vnd.github+json" };
8304
8338
  MAX_REQUESTS = 4;
8339
+ POLICY_RULESET_VERSION = 2;
8305
8340
  AI_SIGNAL_PATTERNS = [
8306
8341
  { label: "AI", re: /\bAI\b/i },
8307
8342
  { label: "artificial intelligence", re: /artificial intelligence/i },
@@ -8313,11 +8348,47 @@ var init_repo_policy = __esm({
8313
8348
  { label: "generative", re: /\bgenerative\b/i },
8314
8349
  { label: "machine-generated", re: /machine[\s-]generated/i }
8315
8350
  ];
8351
+ AI_TERM = "(?:ai|llms?|generative(?:\\s+ai)?|artificial intelligence|language models?|copilot|chatgpt|claude|machine[\\s-]generated)";
8352
+ PROHIBITED_PATTERNS = [
8353
+ new RegExp(`prohibit\\w*[^.\\n]{0,60}\\b${AI_TERM}`, "i"),
8354
+ /did not write the code yourself/i,
8355
+ new RegExp(`(?:not|never|don'?t|won'?t)\\s+accept\\w*[^.\\n]{0,60}\\b${AI_TERM}`, "i"),
8356
+ new RegExp(`\\bno\\s+${AI_TERM}[^.\\n]{0,40}\\b(?:prs?|pull requests?|contributions?|code|patch(?:es)?|commits?|submissions?)\\b`, "i"),
8357
+ new RegExp(
8358
+ `\\b${AI_TERM}[^.\\n]{0,60}\\b(?:is|are|will be)\\s+(?:\\w+\\s+)?(?:not accepted|not allowed|banned|rejected|removed|closed|reverted|prohibited|forbidden)`,
8359
+ "i"
8360
+ )
8361
+ ];
8362
+ DISCLOSURE_PATTERNS = [
8363
+ new RegExp(`disclos\\w+[^.\\n]{0,60}\\b${AI_TERM}`, "i"),
8364
+ new RegExp(`\\b${AI_TERM}[^.\\n]{0,60}disclos`, "i"),
8365
+ new RegExp(`\\b${AI_TERM}[\\s-]assist\\w*[^.\\n]{0,60}\\b(?:must|should|required?)\\b`, "i"),
8366
+ // PR-template checkbox, e.g. "- [ ] I used AI tools and have reviewed the output"
8367
+ new RegExp(`\\[ \\][^\\n]{0,80}\\b${AI_TERM}`, "i")
8368
+ ];
8369
+ REQUIREMENT_PATTERNS = [
8370
+ // `/take` bot first: its docs usually also say "assign", and the bot is the
8371
+ // more specific expectation (post exactly `/take`, not a prose request).
8372
+ { kind: "take-bot", re: /(?:^|[\s`"'(])\/take\b/m },
8373
+ { kind: "assignment-required", re: /(?:request|ask|wait)[^.\n]{0,40}\bassign/i },
8374
+ { kind: "assignment-required", re: /\bassigned before\b/i },
8375
+ { kind: "assignment-required", re: /\bself[\s-]assign/i },
8376
+ { kind: "assignment-required", re: /do not (?:open|submit)[^.\n]{0,40}\b(?:prs?|pull requests?)\b/i },
8377
+ { kind: "cla-required", re: /\bCLA\b/ },
8378
+ { kind: "cla-required", re: /contributor licen[cs]e agreement/i },
8379
+ { kind: "discussion-first", re: /open an issue (?:first|before)/i },
8380
+ { kind: "discussion-first", re: /discuss\w*[^.\n]{0,40}\bbefore\b/i }
8381
+ ];
8316
8382
  CANDIDATE_GROUPS = [
8317
8383
  ["CONTRIBUTING.md", ".github/CONTRIBUTING.md", "docs/CONTRIBUTING.md"],
8318
- [".github/PULL_REQUEST_TEMPLATE.md", "PULL_REQUEST_TEMPLATE.md"],
8319
- ["AGENTS.md"]
8384
+ ["AGENTS.md", "AGENTS.MD"],
8385
+ [".github/PULL_REQUEST_TEMPLATE.md", "PULL_REQUEST_TEMPLATE.md"]
8320
8386
  ];
8387
+ VERDICT_SEVERITY = {
8388
+ "ai-mentioned": 1,
8389
+ "disclosure-required": 2,
8390
+ prohibited: 3
8391
+ };
8321
8392
  }
8322
8393
  });
8323
8394
 
@@ -9010,17 +9081,38 @@ function buildSubmitBody(issueNumber, opts = {}) {
9010
9081
  return `${closesLine}${main}${AI_DISCLOSURE_NOTE}`;
9011
9082
  }
9012
9083
  function printPolicySection(policy) {
9013
- if (policy.status === "flagged") {
9084
+ const verdict = policy.verdict ?? (policy.status === "flagged" ? "ai-mentioned" : policy.status);
9085
+ if (verdict === "prohibited") {
9086
+ console.log("\n POLICY: \u2717 this repo PROHIBITS AI-generated contributions \u2014 READ BEFORE CLAIMING:");
9087
+ } else if (verdict === "disclosure-required") {
9088
+ console.log("\n POLICY: \u26A0 this repo requires disclosing AI assistance \u2014 READ BEFORE WORKING:");
9089
+ } else if (verdict === "ai-mentioned") {
9014
9090
  console.log("\n POLICY: \u26A0 possible AI-assistance policy language found \u2014 READ BEFORE WORKING:");
9015
- for (const hit of policy.hits) {
9016
- console.log(` [${hit.file}]`);
9017
- for (const line of hit.excerpt.split("\n")) console.log(` ${line}`);
9018
- }
9019
9091
  } else if (policy.status === "unavailable") {
9020
9092
  console.log("\n POLICY: could not read this repo's CONTRIBUTING/PR-template/AGENTS docs (rate-limited or unreachable) \u2014 read them yourself before working.");
9021
9093
  } else {
9022
9094
  console.log(" policy: no AI-assistance policy language detected in CONTRIBUTING/PR-template/AGENTS docs");
9023
9095
  }
9096
+ for (const hit of policy.hits ?? []) {
9097
+ console.log(` [${hit.file}]`);
9098
+ for (const line of hit.excerpt.split("\n")) console.log(` ${line}`);
9099
+ }
9100
+ const requirements = policy.requirements ?? [];
9101
+ if (requirements.length > 0) {
9102
+ console.log("\n REQUIREMENTS: this repo sets contribution expectations \u2014 in its own words:");
9103
+ for (const req of requirements) {
9104
+ console.log(` ${req.kind} [${req.file}]`);
9105
+ for (const line of req.excerpt.split("\n")) console.log(` ${line}`);
9106
+ }
9107
+ }
9108
+ const assignment = policy.assignment ?? "none";
9109
+ if (assignment === "take-bot") {
9110
+ console.log(" assignment: repo self-assigns via a /take comment \u2014 `claim start` will post `/take` on the issue");
9111
+ } else if (assignment === "required") {
9112
+ console.log(" assignment: repo expects you to request assignment \u2014 `claim start` will post a request comment on the issue");
9113
+ } else {
9114
+ console.log(" assignment: no assignment expectation found in repo docs \u2014 `claim start` will not comment (pass --assign to request anyway)");
9115
+ }
9024
9116
  }
9025
9117
  var pExecFile = promisify2(execFile2);
9026
9118
  async function sh(cmd, args, opts = {}) {
@@ -9291,9 +9383,9 @@ async function fetchIssue(repoFullName, issueNumber) {
9291
9383
  }
9292
9384
  var ASSIGNMENT_MARKER = "<!-- terminalhire:assignment-request -->";
9293
9385
  var TAKE_BOT_REPOS = /* @__PURE__ */ new Set(["paradedb/paradedb"]);
9294
- function buildAssignmentComment(repoFullName) {
9295
- const usesTakeBot = TAKE_BOT_REPOS.has(repoFullName.toLowerCase());
9296
- const body = usesTakeBot ? "/take" : `I'd like to work on this \u2014 could I be assigned? Thanks!
9386
+ function buildAssignmentComment(repoFullName, opts = {}) {
9387
+ const usesTakeBot = TAKE_BOT_REPOS.has(repoFullName.toLowerCase()) || Boolean(opts.takeBot);
9388
+ const body = usesTakeBot ? "/take" : `Hi! I'd like to work on this issue. Could you assign it to me? Thanks!
9297
9389
 
9298
9390
  ${ASSIGNMENT_MARKER}`;
9299
9391
  return { usesTakeBot, body };
@@ -9314,6 +9406,11 @@ async function hasPriorAssignmentRequest(repoFullName, issueNumber, login) {
9314
9406
  return false;
9315
9407
  }
9316
9408
  }
9409
+ function shouldRequestAssignment(claim, flags = {}) {
9410
+ const expectation = claim && claim.policy ? claim.policy.assignment : void 0;
9411
+ const post = Boolean(flags.assign) || expectation === void 0 || expectation === "required" || expectation === "take-bot";
9412
+ return { post, takeBot: expectation === "take-bot" };
9413
+ }
9317
9414
  async function requestIssueAssignment(claim, flags = {}, ghUser) {
9318
9415
  if (flags["no-assign"]) {
9319
9416
  console.log(" (--no-assign \u2014 skipping the assignment request; request it manually before working)");
@@ -9322,6 +9419,11 @@ async function requestIssueAssignment(claim, flags = {}, ghUser) {
9322
9419
  const parsed = parseGitHubUrl(claim.issueUrl);
9323
9420
  if (!parsed) return;
9324
9421
  const { repoFullName, number } = parsed;
9422
+ const expectation = shouldRequestAssignment(claim, flags);
9423
+ if (!expectation.post) {
9424
+ console.log(" (no assignment expectation found in repo docs \u2014 pass --assign to request assignment anyway)");
9425
+ return;
9426
+ }
9325
9427
  let login = ghUser;
9326
9428
  if (!login) {
9327
9429
  try {
@@ -9336,11 +9438,21 @@ async function requestIssueAssignment(claim, flags = {}, ghUser) {
9336
9438
  console.log(` \u2713 Already assigned to @${login} on ${repoFullName}#${number}.`);
9337
9439
  return;
9338
9440
  }
9339
- const { usesTakeBot, body } = buildAssignmentComment(repoFullName);
9441
+ const { usesTakeBot, body } = buildAssignmentComment(repoFullName, { takeBot: expectation.takeBot });
9340
9442
  if (!usesTakeBot && await hasPriorAssignmentRequest(repoFullName, number, login)) {
9341
9443
  console.log(` \u2713 Assignment already requested on ${repoFullName}#${number}.`);
9342
9444
  return;
9343
9445
  }
9446
+ if (process.stdin.isTTY) {
9447
+ console.log(`
9448
+ About to comment on ${repoFullName}#${number} as @${login}:`);
9449
+ for (const line of body.split("\n")) console.log(` ${line}`);
9450
+ const go = await confirm(" Post it? (y/N) ");
9451
+ if (!go) {
9452
+ console.log(" (skipped \u2014 request assignment manually before working)");
9453
+ return;
9454
+ }
9455
+ }
9344
9456
  try {
9345
9457
  await sh("gh", ["issue", "comment", String(number), "--repo", repoFullName, "--body", body]);
9346
9458
  console.log(
@@ -9473,7 +9585,7 @@ function fmtContestedWarning(b) {
9473
9585
  async function cmdRecord(arg, flags = {}) {
9474
9586
  const claims = await Promise.resolve().then(() => (init_claims(), claims_exports));
9475
9587
  if (!arg) {
9476
- console.error("Usage: terminalhire claim record <bountyId|issueUrl> [--ack-policy] [--ack-contested]");
9588
+ console.error("Usage: terminalhire claim record <bountyId|issueUrl> [--ack-policy] [--ack-policy-prohibited] [--ack-contested]");
9477
9589
  console.error(" Run `terminalhire bounties` first to populate the local index cache,");
9478
9590
  console.error(" then pass the id shown in its output \u2014 or pass a GitHub issue URL directly.");
9479
9591
  process.exit(1);
@@ -9511,8 +9623,25 @@ ${contestedWarning}`);
9511
9623
  const policy = await checkRepoPolicy2(b.repoFullName);
9512
9624
  printPolicySection(policy);
9513
9625
  let ackedAt = null;
9514
- if (policy.status === "flagged" || policy.status === "unavailable") {
9515
- const reason = policy.status === "flagged" ? "This repo may prohibit AI-generated contributions" : "This repo's contribution policy could not be checked";
9626
+ if (policy.verdict === "prohibited") {
9627
+ let acked = Boolean(flags["ack-policy-prohibited"]);
9628
+ if (!acked && process.stdin.isTTY) {
9629
+ acked = await confirm(
9630
+ "\n This repo PROHIBITS AI-generated contributions \u2014 continuing means everything you submit must be hand-written by you. Continue? (y/N) "
9631
+ );
9632
+ }
9633
+ if (!acked) {
9634
+ console.error(
9635
+ `
9636
+ terminalhire claim: refusing to record \u2014 ${b.repoFullName} prohibits AI-generated contributions.
9637
+ Read the excerpts above. If you will hand-write the work yourself, re-run with
9638
+ --ack-policy-prohibited (or confirm interactively). --ack-policy is not enough here.`
9639
+ );
9640
+ process.exit(1);
9641
+ }
9642
+ ackedAt = (/* @__PURE__ */ new Date()).toISOString();
9643
+ } else if (policy.status === "flagged" || policy.status === "unavailable") {
9644
+ const reason = policy.status === "flagged" ? "This repo has AI-assistance policy language" : "This repo's contribution policy could not be checked";
9516
9645
  let acked = Boolean(flags["ack-policy"]);
9517
9646
  if (!acked && process.stdin.isTTY) {
9518
9647
  acked = await confirm(`
@@ -9542,7 +9671,18 @@ terminalhire claim: refusing to record \u2014 read ${b.repoFullName}'s contribut
9542
9671
  amountUSD: b.amountUSD,
9543
9672
  openPRsAtClaim: b.openPRs,
9544
9673
  kind,
9545
- policy: { status: policy.status, ackedAt }
9674
+ // Persist the full audit verdict (not just the coarse status) so `claim
9675
+ // start` can drive the assignment decision from it and `claim audit` can
9676
+ // show what ruleset the dev acknowledged. Excerpts are NOT persisted —
9677
+ // the record stores the conclusion; the docs stay the source of truth.
9678
+ policy: {
9679
+ status: policy.status,
9680
+ verdict: policy.verdict,
9681
+ assignment: policy.assignment,
9682
+ requirements: (policy.requirements ?? []).map((r) => r.kind),
9683
+ rulesetVersion: policy.rulesetVersion,
9684
+ ackedAt
9685
+ }
9546
9686
  });
9547
9687
  } catch (err) {
9548
9688
  console.error(`terminalhire claim: ${err.message ?? err}`);
@@ -9604,7 +9744,14 @@ async function cmdPreview(arg, { json } = {}) {
9604
9744
  openPRs: b.openPRs,
9605
9745
  assignees: b.assignees,
9606
9746
  contested: isContested(b),
9607
- policy: { status: policy.status, hits: policy.hits }
9747
+ policy: {
9748
+ status: policy.status,
9749
+ verdict: policy.verdict,
9750
+ assignment: policy.assignment,
9751
+ rulesetVersion: policy.rulesetVersion,
9752
+ hits: policy.hits,
9753
+ requirements: policy.requirements
9754
+ }
9608
9755
  }) + "\n"
9609
9756
  );
9610
9757
  return;
@@ -10736,6 +10883,7 @@ export {
10736
10883
  run,
10737
10884
  selectCompetingPrs,
10738
10885
  selectPushRemote,
10886
+ shouldRequestAssignment,
10739
10887
  startBranchFor,
10740
10888
  workDirFor
10741
10889
  };
@@ -1413,9 +1413,9 @@ var init_keytar = __esm({
1413
1413
  }
1414
1414
  });
1415
1415
 
1416
- // node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node
1416
+ // node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/agent-a0bc554500876cd51/node_modules/keytar/build/Release/keytar.node
1417
1417
  var require_keytar = __commonJS({
1418
- "node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node"(exports, module) {
1418
+ "node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/agent-a0bc554500876cd51/node_modules/keytar/build/Release/keytar.node"(exports, module) {
1419
1419
  "use strict";
1420
1420
  init_keytar();
1421
1421
  try {
@@ -8062,9 +8062,9 @@ var init_keytar = __esm({
8062
8062
  }
8063
8063
  });
8064
8064
 
8065
- // node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node
8065
+ // node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/agent-a0bc554500876cd51/node_modules/keytar/build/Release/keytar.node
8066
8066
  var require_keytar = __commonJS({
8067
- "node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node"(exports, module) {
8067
+ "node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/agent-a0bc554500876cd51/node_modules/keytar/build/Release/keytar.node"(exports, module) {
8068
8068
  "use strict";
8069
8069
  init_keytar();
8070
8070
  try {
@@ -8467,6 +8467,31 @@ function linkTitle(title, url) {
8467
8467
  return href ? `${safeTitle} (${href})` : safeTitle;
8468
8468
  }
8469
8469
 
8470
+ // bin/match-slots.js
8471
+ var ROTATE_WINDOW_MS = 5 * 60 * 1e3;
8472
+ var ROLE_HEAD_ROTATE_MS = 60 * 60 * 1e3;
8473
+ function rotate(list, now, windowMs = ROTATE_WINDOW_MS) {
8474
+ if (!Array.isArray(list) || list.length === 0) return [];
8475
+ const idx = Math.floor(now / windowMs) % list.length;
8476
+ return [...list.slice(idx), ...list.slice(0, idx)];
8477
+ }
8478
+
8479
+ // bin/directory-select.js
8480
+ var DEVS_STABLE_HEAD = 5;
8481
+ var DEVS_ROTATE_WINDOW = 15;
8482
+ var DEVS_ROTATE_MS = 5 * 60 * 1e3;
8483
+ function selectDirectoryDevs(results, { limit, now = Date.now() } = {}) {
8484
+ const list = Array.isArray(results) ? results : [];
8485
+ if (list.length === 0) return [];
8486
+ if (limit >= list.length) return list;
8487
+ const head = list.slice(0, Math.min(DEVS_STABLE_HEAD, limit));
8488
+ const tailPool = list.slice(head.length, head.length + DEVS_ROTATE_WINDOW);
8489
+ const tailSelected = rotate(tailPool, now, DEVS_ROTATE_MS).slice(0, limit - head.length);
8490
+ const rankOf = new Map(list.map((item, idx) => [item, idx]));
8491
+ const tail = [...tailSelected].sort((a, b) => rankOf.get(a) - rankOf.get(b));
8492
+ return [...head, ...tail];
8493
+ }
8494
+
8470
8495
  // bin/jpi-devs.js
8471
8496
  var API_URL2 = process.env["TERMINALHIRE_API_URL"] ?? process.env["JPI_API_URL"] ?? "https://terminalhire.com";
8472
8497
  var DEFAULT_LIMIT = 10;
@@ -8526,7 +8551,7 @@ async function getDevs({ quiet = false, offline = false } = {}) {
8526
8551
  }
8527
8552
  const cards = index.cards ?? [];
8528
8553
  if (cards.length === 0) return { status: "no-cards", fp };
8529
- let results = match2(fp, cards, SHOW_ALL ? cards.length : LIMIT);
8554
+ let results = match2(fp, cards, cards.length);
8530
8555
  let ownLogin;
8531
8556
  try {
8532
8557
  const { readProfile: readProfile2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
@@ -8534,6 +8559,7 @@ async function getDevs({ quiet = false, offline = false } = {}) {
8534
8559
  } catch {
8535
8560
  }
8536
8561
  results = excludeOwnCard(results, ownLogin);
8562
+ if (!SHOW_ALL) results = selectDirectoryDevs(results, { limit: LIMIT });
8537
8563
  if (results.length === 0) return { status: "no-matches", fp };
8538
8564
  return { status: "ok", results, fp };
8539
8565
  }