Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/gittensory-engine/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,9 @@ rankOpportunities(candidates); // sorted by descending score, each annotated wit

`rankOpportunities` is a stable sort with an explicit index tie-break: candidates with an equal score keep their
input order.

## AI Policy Map

`scanAiPolicyText` and `resolveAiPolicyVerdict` provide the deterministic policy gate used by miner discovery.
They only deny on small, explicit AI-contribution ban phrases in `AI-USAGE.md` or `CONTRIBUTING.md`; ambiguous,
missing, or empty policy text stays allowed so discovery does not invent a ban.
68 changes: 68 additions & 0 deletions packages/gittensory-engine/src/ai-policy-map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
export type AiPolicySource = "AI-USAGE.md" | "CONTRIBUTING.md" | "none";

export type AiPolicyVerdict = {
allowed: boolean;
matchedPhrase: string | null;
source: AiPolicySource;
};

type BanPhrase = {
phrase: string;
pattern: RegExp;
};

const AI_POLICY_ALLOWED: AiPolicyVerdict = {
allowed: true,
matchedPhrase: null,
source: "none",
};

const BAN_PHRASES: BanPhrase[] = [
{
phrase: "no ai-generated pull requests",
pattern: /\bno\s+ai[-\s]+generated\s+(?:pull\s+requests|prs|contributions)\b/i,
},
{
phrase: "ai-generated prs are rejected",
pattern:
/\bai[-\s]+generated\s+(?:prs?|pull\s+requests|contributions?)\s+(?:are|will\s+be)\s+(?:banned|rejected|not\s+accepted)\b/i,
},
{
phrase: "do not submit ai-generated code",
pattern: /\bdo\s+not\s+(?:use|submit)\s+ai[-\s]+(?:written|generated)\s+code\b/i,
},
{
phrase: "llm-generated code is not accepted",
pattern: /\b(?:ai|llm)[-\s]+generated\s+code\s+(?:is|will\s+be)\s+(?:rejected|not\s+accepted)\b/i,
},
];

/**
* Conservative by design (#2305): explicit ban phrases deny a repo, but ambiguous or absent policy text stays
* allowed. False negatives can be tightened with new literal phrases; false positives would hide valid work.
*/
export function scanAiPolicyText(content: string | null | undefined, source: AiPolicySource): AiPolicyVerdict {
const text = content ?? "";
if (source === "none" || text.trim().length === 0) {
return { allowed: true, matchedPhrase: null, source };
}
for (const ban of BAN_PHRASES) {
if (ban.pattern.test(text)) {
return { allowed: false, matchedPhrase: ban.phrase, source };
}
}
return { allowed: true, matchedPhrase: null, source };
}

export function resolveAiPolicyVerdict(docs: {
aiUsage: string | null | undefined;
contributing: string | null | undefined;
}): AiPolicyVerdict {
if (docs.aiUsage !== null && docs.aiUsage !== undefined) {
return scanAiPolicyText(docs.aiUsage, "AI-USAGE.md");
}
if (docs.contributing !== null && docs.contributing !== undefined) {
return scanAiPolicyText(docs.contributing, "CONTRIBUTING.md");
}
return { ...AI_POLICY_ALLOWED };
}
6 changes: 6 additions & 0 deletions packages/gittensory-engine/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,9 @@ export {
type OpportunityRankInput,
} from "./opportunity-ranker.js";
export * from "./governor/rate-limit.js";
export {
resolveAiPolicyVerdict,
scanAiPolicyText,
type AiPolicySource,
type AiPolicyVerdict,
} from "./ai-policy-map.js";
5 changes: 5 additions & 0 deletions packages/gittensory-miner/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ Current scope is intentionally small:

Real miner commands land in follow-up issues.

The package also includes the first metadata-only discovery primitive: `fetchCandidateIssues` lists open issue
metadata across target repos, and `searchCandidateIssues` does the same from a GitHub issue-search query. Both
paths hard-skip repos whose `AI-USAGE.md` or `CONTRIBUTING.md` explicitly bans AI-generated PRs. They perform
GitHub GET requests only, never clone source, never upload source, and never write to GitHub.

## Install

From a local checkout:
Expand Down
72 changes: 72 additions & 0 deletions packages/gittensory-miner/lib/opportunity-fanout.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
export type FanoutTarget = {
owner: string;
repo: string;
};

export type RawCandidateIssue = {
owner: string;
repo: string;
repoFullName: string;
issueNumber: number;
title: string;
labels: string[];
commentsCount: number;
createdAt: string | null;
updatedAt: string | null;
htmlUrl: string | null;
aiPolicyAllowed: true;
aiPolicySource: "AI-USAGE.md" | "CONTRIBUTING.md" | "none";
};

export type CandidateIssueWarning = {
repoFullName: string;
stage: string;
message: string;
};

export type CandidateIssueSummary = {
issues: RawCandidateIssue[];
rateLimitRemaining: number | null;
rateLimitResetAt: string | null;
warnings: CandidateIssueWarning[];
};

export function fetchCandidateIssuesWithSummary(
targets: FanoutTarget[],
githubToken: string,
options?: {
apiBaseUrl?: string;
concurrency?: number;
perPage?: number;
},
): Promise<CandidateIssueSummary>;

export function fetchCandidateIssues(
targets: FanoutTarget[],
githubToken: string,
options?: {
apiBaseUrl?: string;
concurrency?: number;
perPage?: number;
},
): Promise<RawCandidateIssue[]>;

export function searchCandidateIssuesWithSummary(
searchQuery: string,
githubToken: string,
options?: {
apiBaseUrl?: string;
concurrency?: number;
perPage?: number;
},
): Promise<CandidateIssueSummary>;

export function searchCandidateIssues(
searchQuery: string,
githubToken: string,
options?: {
apiBaseUrl?: string;
concurrency?: number;
perPage?: number;
},
): Promise<RawCandidateIssue[]>;
Loading
Loading