@cursor/july 0.1.55 → 0.1.61
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/dist/bin/agent-serve.js +30 -23
- package/dist/channels/github/github-channel.d.ts +12 -0
- package/dist/channels/github/github-channel.d.ts.map +1 -1
- package/dist/channels/github/github-channel.js +67 -15
- package/dist/channels/github/types.d.ts +27 -1
- package/dist/channels/github/types.d.ts.map +1 -1
- package/dist/channels/origin/cursor-account.d.ts +22 -0
- package/dist/channels/origin/cursor-account.d.ts.map +1 -0
- package/dist/channels/origin/cursor-account.js +35 -0
- package/dist/channels/origin/events.d.ts +10 -0
- package/dist/channels/origin/events.d.ts.map +1 -0
- package/dist/channels/origin/events.js +27 -0
- package/dist/channels/origin/index.d.ts +29 -0
- package/dist/channels/origin/index.d.ts.map +1 -0
- package/dist/channels/origin/index.js +28 -0
- package/dist/channels/origin/origin-channel.d.ts +68 -0
- package/dist/channels/origin/origin-channel.d.ts.map +1 -0
- package/dist/channels/origin/origin-channel.js +567 -0
- package/dist/channels/origin/origin-webhook.d.ts +38 -0
- package/dist/channels/origin/origin-webhook.d.ts.map +1 -0
- package/dist/channels/origin/origin-webhook.js +151 -0
- package/dist/channels/origin/types.d.ts +152 -0
- package/dist/channels/origin/types.d.ts.map +1 -0
- package/dist/channels/origin/types.js +1 -0
- package/dist/channels/slack/doctor.d.ts +17 -1
- package/dist/channels/slack/doctor.d.ts.map +1 -1
- package/dist/channels/slack/doctor.js +186 -10
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +4 -4
- package/dist/docs/assets/{app.CVQxhV85.js → app.CW3KJh72.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.BuymfBAo.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.QAPzJjql.js → VPLocalSearchBox.Bk6EWpPj.js} +1 -1
- package/dist/docs/assets/chunks/{theme.CSxTCOFu.js → theme.Bz9AodBA.js} +2 -2
- package/dist/docs/assets/{example-agents_fsd.md.DPz9ezO4.js → example-agents_fsd.md.ZeGEpAw_.js} +2 -2
- package/dist/docs/assets/{example-agents_fsd.md.DPz9ezO4.lean.js → example-agents_fsd.md.ZeGEpAw_.lean.js} +1 -1
- package/dist/docs/assets/{example-agents_security-reviewer.md.Dkf1gyo6.js → example-agents_security-reviewer.md.DBL1TwtP.js} +2 -2
- package/dist/docs/assets/{example-agents_security-reviewer.md.Dkf1gyo6.lean.js → example-agents_security-reviewer.md.DBL1TwtP.lean.js} +1 -1
- package/dist/docs/assets/guides_convert-automation.md.BhMzTkE5.js +12 -0
- package/dist/docs/assets/guides_convert-automation.md.BhMzTkE5.lean.js +1 -0
- package/dist/docs/assets/{guides_github.md.B8QKtIpw.js → guides_github.md.CLLQJKRB.js} +4 -4
- package/dist/docs/assets/{guides_github.md.B8QKtIpw.lean.js → guides_github.md.CLLQJKRB.lean.js} +1 -1
- package/dist/docs/assets/index.md.DhRHS_-L.js +5 -0
- package/dist/docs/assets/{index.md.COeoSkcF.lean.js → index.md.DhRHS_-L.lean.js} +1 -1
- package/dist/docs/assets/{reference_cli.md.D_CCfG8R.js → reference_cli.md.Cl14Jfo3.js} +8 -2
- package/dist/docs/assets/{reference_cli.md.D_CCfG8R.lean.js → reference_cli.md.Cl14Jfo3.lean.js} +1 -1
- package/dist/docs/assets/scaffolding-agents.md.CrWSA0Ko.js +1 -0
- package/dist/docs/assets/{scaffolding-agents.md.mkc3B_ZW.lean.js → scaffolding-agents.md.CrWSA0Ko.lean.js} +1 -1
- package/dist/docs/assets/templates_agentic-owners.md.BkTLORaU.js +14 -0
- package/dist/docs/assets/templates_agentic-owners.md.BkTLORaU.lean.js +1 -0
- package/dist/docs/assets/templates_demo.md.Bgd6MBaZ.js +4 -0
- package/dist/docs/assets/templates_demo.md.Bgd6MBaZ.lean.js +1 -0
- package/dist/docs/assets/templates_pr-autofixer.md.DcmoeUNZ.js +8 -0
- package/dist/docs/assets/templates_pr-autofixer.md.DcmoeUNZ.lean.js +1 -0
- package/dist/docs/assets/templates_security-reviewer.md.C0yIUaYs.js +2 -0
- package/dist/docs/assets/templates_security-reviewer.md.C0yIUaYs.lean.js +1 -0
- package/dist/docs/building-with-agents.html +4 -4
- package/dist/docs/concepts.html +5 -5
- package/dist/docs/deployment.html +4 -4
- package/dist/docs/evals.html +4 -4
- package/dist/docs/example-agents/approval-buddy.html +4 -4
- package/dist/docs/example-agents/benny.html +4 -4
- package/dist/docs/example-agents/bugbot.html +4 -4
- package/dist/docs/example-agents/codebase-wiki.html +4 -4
- package/dist/docs/example-agents/codeowners-review.html +4 -4
- package/dist/docs/example-agents/concierge.html +4 -4
- package/dist/docs/example-agents/fsd.html +5 -5
- package/dist/docs/example-agents/index.html +4 -4
- package/dist/docs/example-agents/knowledge-base.html +4 -4
- package/dist/docs/example-agents/oncall.html +4 -4
- package/dist/docs/example-agents/security-reviewer.html +5 -5
- package/dist/docs/example-agents/slack-agent.html +4 -4
- package/dist/docs/example-agents/weather-agent.html +4 -4
- package/dist/docs/guides/agent-to-agent.html +4 -4
- package/dist/docs/guides/cloud-runtime.html +4 -4
- package/dist/docs/guides/convert-automation.html +36 -0
- package/dist/docs/guides/github.html +7 -7
- package/dist/docs/guides/human-in-the-loop.html +4 -4
- package/dist/docs/guides/mcp-oauth.html +4 -4
- package/dist/docs/guides/opentelemetry.html +4 -4
- package/dist/docs/guides/slack.html +4 -4
- package/dist/docs/guides/webhooks.html +4 -4
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +4 -4
- package/dist/docs/index.html +6 -6
- package/dist/docs/quickstart.html +4 -4
- package/dist/docs/reference/agent-config.html +4 -4
- package/dist/docs/reference/artifacts.html +4 -4
- package/dist/docs/reference/channels.html +4 -4
- package/dist/docs/reference/cli.html +12 -6
- package/dist/docs/reference/connections.html +4 -4
- package/dist/docs/reference/hooks.html +4 -4
- package/dist/docs/reference/http-api.html +4 -4
- package/dist/docs/reference/instructions.html +4 -4
- package/dist/docs/reference/playground.html +4 -4
- package/dist/docs/reference/project-layout.html +4 -4
- package/dist/docs/reference/prompt.html +4 -4
- package/dist/docs/reference/schedules.html +4 -4
- package/dist/docs/reference/sessions.html +4 -4
- package/dist/docs/reference/skills.html +4 -4
- package/dist/docs/reference/subagents.html +4 -4
- package/dist/docs/reference/tools.html +4 -4
- package/dist/docs/scaffolding-agents.html +5 -5
- package/dist/docs/storage.html +4 -4
- package/dist/docs/templates/agentic-owners.html +38 -0
- package/dist/docs/templates/demo.html +28 -0
- package/dist/docs/templates/pr-autofixer.html +32 -0
- package/dist/docs/templates/security-reviewer.html +26 -0
- package/dist/docs/templates/triage.html +5 -5
- package/dist/docs/troubleshooting.html +4 -4
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/internal/cli-ax.d.ts +2 -0
- package/dist/internal/cli-ax.d.ts.map +1 -1
- package/dist/internal/cli-ax.js +20 -0
- package/dist/internal/cli-slack.d.ts +2 -0
- package/dist/internal/cli-slack.d.ts.map +1 -1
- package/dist/internal/cli-slack.js +11 -2
- package/dist/internal/cursor-event-relay.d.ts +59 -11
- package/dist/internal/cursor-event-relay.d.ts.map +1 -1
- package/dist/internal/cursor-event-relay.js +133 -26
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +11 -2
- package/dist/internal/distribution.d.ts.map +1 -1
- package/dist/internal/distribution.js +1 -0
- package/dist/internal/handleAgentServeTrigger.d.ts +33 -6
- package/dist/internal/handleAgentServeTrigger.d.ts.map +1 -1
- package/dist/internal/handleAgentServeTrigger.js +97 -19
- package/dist/internal/init-interview.d.ts +67 -0
- package/dist/internal/init-interview.d.ts.map +1 -0
- package/dist/internal/init-interview.js +300 -0
- package/dist/internal/init-project.d.ts +4 -0
- package/dist/internal/init-project.d.ts.map +1 -1
- package/dist/internal/init-project.js +40 -16
- package/dist/internal/mcp-endpoint.d.ts +7 -0
- package/dist/internal/mcp-endpoint.d.ts.map +1 -1
- package/dist/internal/mcp-endpoint.js +7 -0
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +159 -57
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +20 -21
- package/dist/internal/session-links.d.ts +14 -0
- package/dist/internal/session-links.d.ts.map +1 -0
- package/dist/internal/session-links.js +69 -0
- package/dist/internal/sessions-client.d.ts.map +1 -1
- package/dist/internal/sessions-client.js +1 -0
- package/dist/playground/assets/index-ClbuaLpm.css +1 -0
- package/dist/playground/assets/{index-0oqXUkrR.js → index-SqkR13k_.js} +39 -39
- package/dist/playground/index.html +2 -2
- package/dist/types.d.ts +19 -12
- package/dist/types.d.ts.map +1 -1
- package/docs/README.md +15 -0
- package/docs/example-agents/fsd.md +3 -0
- package/docs/example-agents/security-reviewer.md +3 -0
- package/docs/guides/convert-automation.md +176 -0
- package/docs/guides/github.md +6 -2
- package/docs/reference/cli.md +34 -10
- package/docs/scaffolding-agents.md +3 -0
- package/docs/templates/agentic-owners.md +97 -0
- package/docs/templates/demo.md +84 -0
- package/docs/templates/pr-autofixer.md +136 -0
- package/docs/templates/security-reviewer.md +89 -0
- package/package.json +9 -1
- package/skills/github/SKILL.md +4 -5
- package/src/bin/agent-serve.ts +13 -6
- package/src/channels/github/github-channel.ts +95 -18
- package/src/channels/github/types.ts +31 -0
- package/src/channels/origin/cursor-account.ts +64 -0
- package/src/channels/origin/events.ts +41 -0
- package/src/channels/origin/index.ts +29 -0
- package/src/channels/origin/origin-channel.ts +784 -0
- package/src/channels/origin/origin-webhook.ts +202 -0
- package/src/channels/origin/types.ts +186 -0
- package/src/channels/slack/doctor.ts +218 -5
- package/src/index.ts +3 -1
- package/src/internal/cli-ax.ts +23 -0
- package/src/internal/cli-slack.ts +11 -1
- package/src/internal/cursor-event-relay.ts +193 -31
- package/src/internal/discovery.ts +15 -5
- package/src/internal/distribution.ts +1 -0
- package/src/internal/handleAgentServeTrigger.ts +129 -19
- package/src/internal/init-interview.ts +384 -0
- package/src/internal/init-project.ts +70 -15
- package/src/internal/mcp-endpoint.ts +7 -0
- package/src/internal/server.ts +188 -58
- package/src/internal/session-engine.ts +5 -21
- package/src/internal/session-links.ts +88 -0
- package/src/internal/sessions-client.ts +1 -0
- package/src/types.ts +19 -12
- package/templates/agentic-owners/README.md +73 -0
- package/templates/agentic-owners/agent/agent.ts +15 -0
- package/templates/agentic-owners/agent/channels/github.ts +85 -0
- package/templates/agentic-owners/agent/instructions.md +34 -0
- package/templates/agentic-owners/agent/lib/config.ts +13 -0
- package/templates/agentic-owners/agent/lib/github.ts +241 -0
- package/templates/agentic-owners/agent/lib/owners/README.md +50 -0
- package/templates/agentic-owners/agent/lib/owners/constants.ts +22 -0
- package/templates/agentic-owners/agent/lib/owners/decision.ts +485 -0
- package/templates/agentic-owners/agent/lib/owners/handles.ts +54 -0
- package/templates/agentic-owners/agent/lib/owners/match.ts +177 -0
- package/templates/agentic-owners/agent/lib/owners/matching.ts +95 -0
- package/templates/agentic-owners/agent/lib/owners/parse.ts +589 -0
- package/templates/agentic-owners/agent/lib/owners/policyMode.ts +65 -0
- package/templates/agentic-owners/agent/lib/owners/prompt.ts +66 -0
- package/templates/agentic-owners/agent/lib/owners/prompts/base-review.md +105 -0
- package/templates/agentic-owners/agent/lib/owners/types.ts +45 -0
- package/templates/agentic-owners/agent/lib/review.ts +777 -0
- package/templates/agentic-owners/agent/tools/record_review.ts +69 -0
- package/templates/agentic-owners/evals/evals.config.ts +5 -0
- package/templates/agentic-owners/evals/owners.eval.ts +116 -0
- package/templates/agentic-owners/package.json +20 -0
- package/templates/agentic-owners/tsconfig.json +12 -0
- package/templates/demo/README.md +33 -0
- package/templates/demo/agent/agent.ts +15 -0
- package/templates/demo/agent/channels/github.ts +50 -0
- package/templates/demo/agent/channels/queue.ts +30 -0
- package/templates/demo/agent/instructions.md +8 -0
- package/templates/demo/agent/lib/collect.ts +78 -0
- package/templates/demo/agent/tools/record_demo.ts +40 -0
- package/templates/demo/evals/demo.eval.ts +33 -0
- package/templates/demo/evals/evals.config.ts +5 -0
- package/templates/demo/package.json +18 -0
- package/templates/demo/tsconfig.json +12 -0
- package/templates/pr-autofixer/README.md +44 -0
- package/templates/pr-autofixer/agent/agent.ts +22 -0
- package/templates/pr-autofixer/agent/channels/drive.ts +40 -0
- package/templates/pr-autofixer/agent/channels/github.ts +167 -0
- package/templates/pr-autofixer/agent/channels/slack.ts +17 -0
- package/templates/pr-autofixer/agent/hooks/bind-agent.ts +19 -0
- package/templates/pr-autofixer/agent/instructions.md +83 -0
- package/templates/pr-autofixer/agent/lib/affinity.ts +33 -0
- package/templates/pr-autofixer/agent/lib/drive-schemas.ts +15 -0
- package/templates/pr-autofixer/agent/lib/drive.ts +79 -0
- package/templates/pr-autofixer/agent/lib/merge-watch.ts +123 -0
- package/templates/pr-autofixer/agent/lib/pr-state.ts +54 -0
- package/templates/pr-autofixer/agent/lib/repos.ts +19 -0
- package/templates/pr-autofixer/agent/lib/start-drive.ts +122 -0
- package/templates/pr-autofixer/agent/lib/webhook-buffer.ts +225 -0
- package/templates/pr-autofixer/agent/lib/webhook-routing.ts +231 -0
- package/templates/pr-autofixer/agent/storage.ts +7 -0
- package/templates/pr-autofixer/agent/tools/drive_pr.ts +22 -0
- package/templates/pr-autofixer/evals/coordinator.eval.ts +16 -0
- package/templates/pr-autofixer/evals/evals.config.ts +5 -0
- package/templates/pr-autofixer/init.json +25 -0
- package/templates/pr-autofixer/package.json +18 -0
- package/templates/pr-autofixer/tsconfig.json +12 -0
- package/templates/security-reviewer/README.md +31 -0
- package/templates/security-reviewer/agent/agent.ts +15 -0
- package/templates/security-reviewer/agent/channels/github.ts +46 -0
- package/templates/security-reviewer/agent/instructions.md +22 -0
- package/templates/security-reviewer/agent/lib/repos.ts +5 -0
- package/templates/security-reviewer/agent/tools/post_findings.ts +72 -0
- package/templates/security-reviewer/evals/evals.config.ts +5 -0
- package/templates/security-reviewer/evals/review.eval.ts +53 -0
- package/templates/security-reviewer/init.json +25 -0
- package/templates/security-reviewer/package.json +18 -0
- package/templates/security-reviewer/tsconfig.json +12 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.BFpWSo6D.js +0 -1
- package/dist/docs/assets/index.md.COeoSkcF.js +0 -5
- package/dist/docs/assets/scaffolding-agents.md.mkc3B_ZW.js +0 -1
- package/dist/playground/assets/index-tHCjJxv4.css +0 -1
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
// Vendored from the Agentic Owners policy core; see README.md in this
|
|
2
|
+
// directory for the deliberate divergences. Validation semantics for the
|
|
3
|
+
// committed policy files this template reads must stay identical to the
|
|
4
|
+
// source's. Bare team slugs stay bare (no hardcoded-org prefix) — the agent
|
|
5
|
+
// resolves them against the watched repository's owner at actuation.
|
|
6
|
+
|
|
7
|
+
import yaml from "yaml";
|
|
8
|
+
import {
|
|
9
|
+
AGENTIC_OWNERS_CONFIG_VERSION,
|
|
10
|
+
AGENTIC_OWNERS_DIR,
|
|
11
|
+
AGENTIC_OWNERS_GENERATED_DIR,
|
|
12
|
+
AGENTIC_OWNERS_POLICY_FILE,
|
|
13
|
+
ROOT_POLICY_SOURCE_PATH,
|
|
14
|
+
} from "./constants.js";
|
|
15
|
+
import {
|
|
16
|
+
parseAgenticOwnersPolicyMode,
|
|
17
|
+
resolvePolicyModeFromFields,
|
|
18
|
+
} from "./policyMode.js";
|
|
19
|
+
import type { AgenticOwnersPathRule } from "./types.js";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* True when `filepath` lives under the generated policy directory
|
|
23
|
+
* (`.agentic-owners/generated/`). These files are produced at review time
|
|
24
|
+
* (e.g. the CI runner's CODEOWNERS codemod) and must be excluded from the
|
|
25
|
+
* agent's changed-file set even if they somehow appear in the diff.
|
|
26
|
+
*/
|
|
27
|
+
export function isGeneratedAgenticOwnersPath(filepath: string): boolean {
|
|
28
|
+
const normalized = filepath.replace(/^\/+/, "");
|
|
29
|
+
return (
|
|
30
|
+
normalized === AGENTIC_OWNERS_GENERATED_DIR ||
|
|
31
|
+
normalized.startsWith(`${AGENTIC_OWNERS_GENERATED_DIR}/`)
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Every `.agentic-owners/policy.yml` path that could govern the changed
|
|
37
|
+
* files: the root policy plus one candidate per ancestor directory.
|
|
38
|
+
*/
|
|
39
|
+
export function listAgenticOwnersConfigPathsForChangedFiles(
|
|
40
|
+
changedFiles: readonly string[]
|
|
41
|
+
): string[] {
|
|
42
|
+
const paths = new Set<string>([
|
|
43
|
+
`${AGENTIC_OWNERS_DIR}/${AGENTIC_OWNERS_POLICY_FILE}`,
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
for (const filepath of changedFiles) {
|
|
47
|
+
const normalized = filepath.replace(/^\/+/, "");
|
|
48
|
+
const segments = normalized.split("/");
|
|
49
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
50
|
+
const prefix = segments.slice(0, index).join("/");
|
|
51
|
+
const base =
|
|
52
|
+
prefix === "" ? AGENTIC_OWNERS_DIR : `${prefix}/${AGENTIC_OWNERS_DIR}`;
|
|
53
|
+
paths.add(`${base}/${AGENTIC_OWNERS_POLICY_FILE}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return [...paths].sort();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export class AgenticOwnersConfigValidationError extends Error {
|
|
61
|
+
readonly issues: readonly string[];
|
|
62
|
+
|
|
63
|
+
constructor(issues: readonly string[]) {
|
|
64
|
+
const message =
|
|
65
|
+
issues.length === 1
|
|
66
|
+
? issues[0]!
|
|
67
|
+
: `Agentic Owners config validation failed:\n- ${issues.join("\n- ")}`;
|
|
68
|
+
super(message);
|
|
69
|
+
this.name = "AgenticOwnersConfigValidationError";
|
|
70
|
+
this.issues = issues;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
75
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function parseVersion(
|
|
79
|
+
value: unknown,
|
|
80
|
+
label: string,
|
|
81
|
+
issues: string[]
|
|
82
|
+
): number | undefined {
|
|
83
|
+
if (value === undefined) {
|
|
84
|
+
issues.push(`${label}: missing version`);
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
if (typeof value !== "number" || !Number.isInteger(value)) {
|
|
88
|
+
issues.push(`${label}: version must be an integer`);
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
if (value !== AGENTIC_OWNERS_CONFIG_VERSION) {
|
|
92
|
+
issues.push(
|
|
93
|
+
`${label}: unsupported version ${value} (expected ${AGENTIC_OWNERS_CONFIG_VERSION})`
|
|
94
|
+
);
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function parsePaths(
|
|
101
|
+
value: unknown,
|
|
102
|
+
label: string,
|
|
103
|
+
issues: string[]
|
|
104
|
+
): string[] | undefined {
|
|
105
|
+
if (value === undefined) {
|
|
106
|
+
issues.push(`${label}: missing paths`);
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
if (
|
|
110
|
+
!Array.isArray(value) ||
|
|
111
|
+
value.some((entry) => typeof entry !== "string")
|
|
112
|
+
) {
|
|
113
|
+
issues.push(`${label}: paths must be a list of strings`);
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
const paths = value
|
|
117
|
+
.map((entry) => entry.trim())
|
|
118
|
+
.filter((entry) => entry !== "");
|
|
119
|
+
if (paths.length === 0) {
|
|
120
|
+
issues.push(`${label}: paths must not be empty`);
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
return paths;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function parseGlobalExclusions({
|
|
127
|
+
value,
|
|
128
|
+
sourcePath,
|
|
129
|
+
issues,
|
|
130
|
+
}: {
|
|
131
|
+
value: unknown;
|
|
132
|
+
sourcePath: string;
|
|
133
|
+
issues: string[];
|
|
134
|
+
}): string[] {
|
|
135
|
+
if (value === undefined) {
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
if (sourcePath !== ROOT_POLICY_SOURCE_PATH) {
|
|
139
|
+
issues.push(
|
|
140
|
+
`${sourcePath}: global_exclusions is only allowed in ${ROOT_POLICY_SOURCE_PATH}`
|
|
141
|
+
);
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
return parsePaths(value, `${sourcePath}: global_exclusions`, issues) ?? [];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function parsePromptPath(
|
|
148
|
+
value: unknown,
|
|
149
|
+
label: string,
|
|
150
|
+
issues: string[]
|
|
151
|
+
): string | undefined {
|
|
152
|
+
if (value === undefined) {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
if (typeof value !== "string") {
|
|
156
|
+
issues.push(`${label}: prompt must be a string filename`);
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
const prompt = value.trim();
|
|
160
|
+
if (prompt === "") {
|
|
161
|
+
issues.push(`${label}: prompt must not be empty`);
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
if (
|
|
165
|
+
prompt.startsWith("/") ||
|
|
166
|
+
prompt.includes("/") ||
|
|
167
|
+
prompt.includes("\\") ||
|
|
168
|
+
prompt.includes("..")
|
|
169
|
+
) {
|
|
170
|
+
issues.push(
|
|
171
|
+
`${label}: prompt must be a filename in the same directory as policy.yml (no paths)`
|
|
172
|
+
);
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
return prompt;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function parseTeamSlug(
|
|
179
|
+
value: unknown,
|
|
180
|
+
label: string,
|
|
181
|
+
issues: string[]
|
|
182
|
+
): string | undefined {
|
|
183
|
+
if (value === undefined) {
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
187
|
+
issues.push(`${label}: team must be a non-empty team slug`);
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
const team = value.trim().replace(/^@/, "");
|
|
191
|
+
if (team.includes("/")) {
|
|
192
|
+
// GitHub team handle form (`org/team`) — names a GitHub team directly.
|
|
193
|
+
if (!/^[^/\s]+\/[^/\s]+$/.test(team)) {
|
|
194
|
+
issues.push(
|
|
195
|
+
`${label}: team must be a team slug or a "org/team" GitHub handle`
|
|
196
|
+
);
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
return team;
|
|
200
|
+
}
|
|
201
|
+
return team;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function parseUserHandle(
|
|
205
|
+
value: unknown,
|
|
206
|
+
label: string,
|
|
207
|
+
issues: string[]
|
|
208
|
+
): string | undefined {
|
|
209
|
+
if (value === undefined) {
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
213
|
+
issues.push(`${label}: user must be a non-empty GitHub handle`);
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
const user = value.trim();
|
|
217
|
+
if (user.includes("/") || user.startsWith("@")) {
|
|
218
|
+
issues.push(
|
|
219
|
+
`${label}: user must be a GitHub handle without org prefix or @`
|
|
220
|
+
);
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
return user;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Dedupe while preserving first-seen order. */
|
|
227
|
+
function dedupe(values: readonly string[]): string[] {
|
|
228
|
+
return [...new Set(values)];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Resolve the owners for one policy record from the singular (`team`/`user`)
|
|
233
|
+
* and plural (`teams`/`users`) forms. Both may be present; entries are merged
|
|
234
|
+
* (singular first) and deduped. Each entry is validated with the same rules as
|
|
235
|
+
* the singular form.
|
|
236
|
+
*/
|
|
237
|
+
function parseOwnerList({
|
|
238
|
+
singular,
|
|
239
|
+
plural,
|
|
240
|
+
label,
|
|
241
|
+
field,
|
|
242
|
+
issues,
|
|
243
|
+
parseEntry,
|
|
244
|
+
}: {
|
|
245
|
+
singular: unknown;
|
|
246
|
+
plural: unknown;
|
|
247
|
+
label: string;
|
|
248
|
+
field: "teams" | "users";
|
|
249
|
+
issues: string[];
|
|
250
|
+
parseEntry: (
|
|
251
|
+
value: unknown,
|
|
252
|
+
label: string,
|
|
253
|
+
issues: string[]
|
|
254
|
+
) => string | undefined;
|
|
255
|
+
}): string[] {
|
|
256
|
+
const owners: string[] = [];
|
|
257
|
+
const singularField = field === "teams" ? "team" : "user";
|
|
258
|
+
const parsedSingular = parseEntry(
|
|
259
|
+
singular,
|
|
260
|
+
`${label}.${singularField}`,
|
|
261
|
+
issues
|
|
262
|
+
);
|
|
263
|
+
if (parsedSingular !== undefined) {
|
|
264
|
+
owners.push(parsedSingular);
|
|
265
|
+
}
|
|
266
|
+
if (plural !== undefined) {
|
|
267
|
+
if (
|
|
268
|
+
!Array.isArray(plural) ||
|
|
269
|
+
plural.some((entry) => typeof entry !== "string")
|
|
270
|
+
) {
|
|
271
|
+
issues.push(`${label}.${field}: ${field} must be a list of strings`);
|
|
272
|
+
} else {
|
|
273
|
+
for (const [index, entry] of plural.entries()) {
|
|
274
|
+
const parsed = parseEntry(entry, `${label}.${field}[${index}]`, issues);
|
|
275
|
+
if (parsed !== undefined) {
|
|
276
|
+
owners.push(parsed);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return dedupe(owners);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function parseBooleanField(
|
|
285
|
+
value: unknown,
|
|
286
|
+
label: string,
|
|
287
|
+
field: string,
|
|
288
|
+
issues: string[]
|
|
289
|
+
): boolean | undefined {
|
|
290
|
+
if (value === undefined) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
if (typeof value !== "boolean") {
|
|
294
|
+
issues.push(`${label}: ${field} must be a boolean`);
|
|
295
|
+
return undefined;
|
|
296
|
+
}
|
|
297
|
+
return value;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function rejectLegacyPolicyFields(
|
|
301
|
+
record: Record<string, unknown>,
|
|
302
|
+
label: string,
|
|
303
|
+
issues: string[]
|
|
304
|
+
): void {
|
|
305
|
+
if (record.rules !== undefined) {
|
|
306
|
+
issues.push(`${label}: use top-level policies instead of rules`);
|
|
307
|
+
}
|
|
308
|
+
if (record.required_approvers !== undefined) {
|
|
309
|
+
issues.push(
|
|
310
|
+
`${label}: required_approvers is no longer supported; set team and human_review_required`
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
if (record.review_required !== undefined) {
|
|
314
|
+
issues.push(
|
|
315
|
+
`${label}: review_required is no longer supported; use human_review_required`
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
if (record.prompt_file !== undefined) {
|
|
319
|
+
issues.push(
|
|
320
|
+
`${label}: prompt_file is no longer supported; use prompt: <filename>`
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function scopeDepthForConfigPath(configPath: string): number {
|
|
326
|
+
const marker = `/${AGENTIC_OWNERS_DIR}/`;
|
|
327
|
+
const index = configPath.lastIndexOf(marker);
|
|
328
|
+
if (index <= 0) {
|
|
329
|
+
return 0;
|
|
330
|
+
}
|
|
331
|
+
const prefix = configPath.slice(0, index);
|
|
332
|
+
if (prefix === "") {
|
|
333
|
+
return 0;
|
|
334
|
+
}
|
|
335
|
+
return prefix.split("/").filter((segment) => segment !== "").length;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function parsePolicyRecord({
|
|
339
|
+
record,
|
|
340
|
+
sourcePath,
|
|
341
|
+
label,
|
|
342
|
+
issues,
|
|
343
|
+
}: {
|
|
344
|
+
record: Record<string, unknown>;
|
|
345
|
+
sourcePath: string;
|
|
346
|
+
label: string;
|
|
347
|
+
issues: string[];
|
|
348
|
+
}): AgenticOwnersPathRule | undefined {
|
|
349
|
+
rejectLegacyPolicyFields(record, label, issues);
|
|
350
|
+
|
|
351
|
+
const paths = parsePaths(record.paths, `${label}.paths`, issues);
|
|
352
|
+
const alwaysApprove = parseBooleanField(
|
|
353
|
+
record.always_approve,
|
|
354
|
+
`${label}.always_approve`,
|
|
355
|
+
"always_approve",
|
|
356
|
+
issues
|
|
357
|
+
);
|
|
358
|
+
const humanReviewRequired = parseBooleanField(
|
|
359
|
+
record.human_review_required,
|
|
360
|
+
`${label}.human_review_required`,
|
|
361
|
+
"human_review_required",
|
|
362
|
+
issues
|
|
363
|
+
);
|
|
364
|
+
const parsedMode = parseAgenticOwnersPolicyMode(
|
|
365
|
+
record.mode,
|
|
366
|
+
`${label}.mode`,
|
|
367
|
+
issues
|
|
368
|
+
);
|
|
369
|
+
const selfReview = parseBooleanField(
|
|
370
|
+
record.self_review,
|
|
371
|
+
`${label}.self_review`,
|
|
372
|
+
"self_review",
|
|
373
|
+
issues
|
|
374
|
+
);
|
|
375
|
+
const prompt = parsePromptPath(record.prompt, `${label}.prompt`, issues);
|
|
376
|
+
const teams = parseOwnerList({
|
|
377
|
+
singular: record.team,
|
|
378
|
+
plural: record.teams,
|
|
379
|
+
label,
|
|
380
|
+
field: "teams",
|
|
381
|
+
issues,
|
|
382
|
+
parseEntry: parseTeamSlug,
|
|
383
|
+
});
|
|
384
|
+
const users = parseOwnerList({
|
|
385
|
+
singular: record.user,
|
|
386
|
+
plural: record.users,
|
|
387
|
+
label,
|
|
388
|
+
field: "users",
|
|
389
|
+
issues,
|
|
390
|
+
parseEntry: parseUserHandle,
|
|
391
|
+
});
|
|
392
|
+
if (record.delegated_automations !== undefined) {
|
|
393
|
+
// Fail loudly rather than validate-and-ignore: nothing in this template
|
|
394
|
+
// consumes automation-delegated approvals.
|
|
395
|
+
issues.push(
|
|
396
|
+
`${label}: delegated_automations is not supported by this template`
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (
|
|
401
|
+
paths === undefined ||
|
|
402
|
+
alwaysApprove === undefined ||
|
|
403
|
+
humanReviewRequired === undefined ||
|
|
404
|
+
selfReview === undefined
|
|
405
|
+
) {
|
|
406
|
+
return undefined;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const mode = resolvePolicyModeFromFields({
|
|
410
|
+
mode: parsedMode,
|
|
411
|
+
humanReviewRequired,
|
|
412
|
+
label,
|
|
413
|
+
issues,
|
|
414
|
+
});
|
|
415
|
+
if (mode === undefined) {
|
|
416
|
+
return undefined;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const scopeDepth = scopeDepthForConfigPath(sourcePath);
|
|
420
|
+
|
|
421
|
+
if (
|
|
422
|
+
alwaysApprove &&
|
|
423
|
+
(teams.length > 0 ||
|
|
424
|
+
users.length > 0 ||
|
|
425
|
+
prompt !== undefined ||
|
|
426
|
+
humanReviewRequired ||
|
|
427
|
+
selfReview ||
|
|
428
|
+
parsedMode !== undefined)
|
|
429
|
+
) {
|
|
430
|
+
issues.push(
|
|
431
|
+
`${label}: always_approve cannot be combined with owners, prompt, human_review_required, self_review, or mode`
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (
|
|
436
|
+
!alwaysApprove &&
|
|
437
|
+
!humanReviewRequired &&
|
|
438
|
+
teams.length === 0 &&
|
|
439
|
+
users.length === 0
|
|
440
|
+
) {
|
|
441
|
+
issues.push(
|
|
442
|
+
scopeDepth === 0
|
|
443
|
+
? `${label}: ownerless root policies must use global_exclusions or set always_approve: true`
|
|
444
|
+
: `${label}: ownerless nested policies must set always_approve: true`
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (humanReviewRequired && teams.length === 0 && users.length === 0) {
|
|
449
|
+
issues.push(
|
|
450
|
+
`${label}: team(s) or user(s) is required when human_review_required is true`
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (
|
|
455
|
+
mode === "notify" &&
|
|
456
|
+
!alwaysApprove &&
|
|
457
|
+
teams.length === 0 &&
|
|
458
|
+
users.length === 0
|
|
459
|
+
) {
|
|
460
|
+
issues.push(`${label}: team(s) or user(s) is required when mode is notify`);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return {
|
|
464
|
+
sourcePath,
|
|
465
|
+
scopeDepth,
|
|
466
|
+
paths,
|
|
467
|
+
teams,
|
|
468
|
+
users,
|
|
469
|
+
team: teams[0],
|
|
470
|
+
user: users[0],
|
|
471
|
+
prompt,
|
|
472
|
+
alwaysApprove,
|
|
473
|
+
mode,
|
|
474
|
+
humanReviewRequired: mode === "hard",
|
|
475
|
+
selfReview,
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function globalExclusionsRule({
|
|
480
|
+
sourcePath,
|
|
481
|
+
paths,
|
|
482
|
+
}: {
|
|
483
|
+
sourcePath: string;
|
|
484
|
+
paths: readonly string[];
|
|
485
|
+
}): AgenticOwnersPathRule {
|
|
486
|
+
return {
|
|
487
|
+
sourcePath,
|
|
488
|
+
scopeDepth: 0,
|
|
489
|
+
globalExclusion: true,
|
|
490
|
+
paths,
|
|
491
|
+
teams: [],
|
|
492
|
+
users: [],
|
|
493
|
+
prompt: undefined,
|
|
494
|
+
alwaysApprove: true,
|
|
495
|
+
mode: "advisory",
|
|
496
|
+
humanReviewRequired: false,
|
|
497
|
+
selfReview: false,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
export function parseAgenticOwnersPolicyYaml({
|
|
502
|
+
sourcePath,
|
|
503
|
+
contents,
|
|
504
|
+
}: {
|
|
505
|
+
sourcePath: string;
|
|
506
|
+
contents: string;
|
|
507
|
+
}): AgenticOwnersPathRule[] {
|
|
508
|
+
const issues: string[] = [];
|
|
509
|
+
let parsed: unknown;
|
|
510
|
+
try {
|
|
511
|
+
parsed = yaml.parse(contents);
|
|
512
|
+
} catch (error) {
|
|
513
|
+
throw new AgenticOwnersConfigValidationError([
|
|
514
|
+
`${sourcePath}: invalid YAML (${error instanceof Error ? error.message : String(error)})`,
|
|
515
|
+
]);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (!isRecord(parsed)) {
|
|
519
|
+
throw new AgenticOwnersConfigValidationError([
|
|
520
|
+
`${sourcePath}: root must be a mapping`,
|
|
521
|
+
]);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
parseVersion(parsed.version, sourcePath, issues);
|
|
525
|
+
const globalExclusions = parseGlobalExclusions({
|
|
526
|
+
value: parsed.global_exclusions,
|
|
527
|
+
sourcePath,
|
|
528
|
+
issues,
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
if (parsed.rules !== undefined) {
|
|
532
|
+
issues.push(`${sourcePath}: use policies instead of rules`);
|
|
533
|
+
}
|
|
534
|
+
if (parsed.required_approvers !== undefined) {
|
|
535
|
+
issues.push(
|
|
536
|
+
`${sourcePath}: required_approvers is no longer supported; use policies with team and human_review_required`
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
if (parsed.review_required !== undefined) {
|
|
540
|
+
issues.push(
|
|
541
|
+
`${sourcePath}: review_required is no longer supported; use human_review_required`
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const policiesValue = parsed.policies;
|
|
546
|
+
if (policiesValue === undefined) {
|
|
547
|
+
issues.push(`${sourcePath}: missing policies`);
|
|
548
|
+
} else if (!Array.isArray(policiesValue)) {
|
|
549
|
+
issues.push(`${sourcePath}: policies must be a list`);
|
|
550
|
+
} else {
|
|
551
|
+
const rules: AgenticOwnersPathRule[] = [];
|
|
552
|
+
for (const [index, entry] of policiesValue.entries()) {
|
|
553
|
+
if (!isRecord(entry)) {
|
|
554
|
+
issues.push(`${sourcePath}: policies[${index}] must be a mapping`);
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
const rule = parsePolicyRecord({
|
|
558
|
+
record: entry,
|
|
559
|
+
sourcePath,
|
|
560
|
+
label: `${sourcePath}: policies[${index}]`,
|
|
561
|
+
issues,
|
|
562
|
+
});
|
|
563
|
+
if (rule !== undefined) {
|
|
564
|
+
rules.push(rule);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
if (issues.length > 0) {
|
|
568
|
+
throw new AgenticOwnersConfigValidationError(issues);
|
|
569
|
+
}
|
|
570
|
+
if (rules.length === 0) {
|
|
571
|
+
throw new AgenticOwnersConfigValidationError([
|
|
572
|
+
`${sourcePath}: policies must not be empty`,
|
|
573
|
+
]);
|
|
574
|
+
}
|
|
575
|
+
if (globalExclusions.length > 0) {
|
|
576
|
+
rules.push(
|
|
577
|
+
globalExclusionsRule({
|
|
578
|
+
sourcePath,
|
|
579
|
+
paths: globalExclusions,
|
|
580
|
+
})
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
return rules;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
throw new AgenticOwnersConfigValidationError(
|
|
587
|
+
issues.length > 0 ? issues : [`${sourcePath}: missing policies`]
|
|
588
|
+
);
|
|
589
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** Explicit review enforcement on a `.agentic-owners` policy rule. */
|
|
2
|
+
export type AgenticOwnersPolicyMode = "hard" | "advisory" | "notify";
|
|
3
|
+
|
|
4
|
+
const POLICY_MODES: readonly AgenticOwnersPolicyMode[] = [
|
|
5
|
+
"hard",
|
|
6
|
+
"advisory",
|
|
7
|
+
"notify",
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
export function parseAgenticOwnersPolicyMode(
|
|
11
|
+
value: unknown,
|
|
12
|
+
label: string,
|
|
13
|
+
issues: string[]
|
|
14
|
+
): AgenticOwnersPolicyMode | undefined {
|
|
15
|
+
if (value === undefined) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
if (typeof value !== "string") {
|
|
19
|
+
issues.push(`${label}: mode must be hard, advisory, or notify`);
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
const normalized = value.trim().toLowerCase();
|
|
23
|
+
if (!POLICY_MODES.includes(normalized as AgenticOwnersPolicyMode)) {
|
|
24
|
+
issues.push(`${label}: mode must be hard, advisory, or notify`);
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
return normalized as AgenticOwnersPolicyMode;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function resolvePolicyModeFromFields(args: {
|
|
31
|
+
mode: AgenticOwnersPolicyMode | undefined;
|
|
32
|
+
humanReviewRequired: boolean;
|
|
33
|
+
label: string;
|
|
34
|
+
issues: string[];
|
|
35
|
+
}): AgenticOwnersPolicyMode | undefined {
|
|
36
|
+
const { mode, humanReviewRequired, label, issues } = args;
|
|
37
|
+
if (mode === undefined) {
|
|
38
|
+
return humanReviewRequired ? "hard" : "advisory";
|
|
39
|
+
}
|
|
40
|
+
if (humanReviewRequired && mode !== "hard") {
|
|
41
|
+
issues.push(
|
|
42
|
+
`${label}: human_review_required: true requires mode: hard (or omit mode)`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
return mode;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function ruleIsHardGate(rule: {
|
|
49
|
+
readonly mode?: AgenticOwnersPolicyMode;
|
|
50
|
+
readonly humanReviewRequired: boolean;
|
|
51
|
+
}): boolean {
|
|
52
|
+
if (rule.mode === "notify") {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
if (rule.mode === "hard") {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
return rule.humanReviewRequired;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function ruleIsNotifyOnly(rule: {
|
|
62
|
+
readonly mode?: AgenticOwnersPolicyMode;
|
|
63
|
+
}): boolean {
|
|
64
|
+
return rule.mode === "notify";
|
|
65
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Vendored from the Agentic Owners policy core; see README.md in this
|
|
2
|
+
// directory.
|
|
3
|
+
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { AGENTIC_OWNERS_MAX_COMPOSED_PROMPT_CHARS } from "./constants.js";
|
|
7
|
+
import { AgenticOwnersConfigValidationError } from "./parse.js";
|
|
8
|
+
import type { AgenticOwnersPathRule } from "./types.js";
|
|
9
|
+
|
|
10
|
+
/** Cap the embedded diff so a huge PR cannot blow the model context budget. */
|
|
11
|
+
export const DEFAULT_MAX_DIFF_CHARS = 200_000;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Base review prompt that EVERY review starts with, shipped as an in-tree
|
|
15
|
+
* asset so it deploys with the agent. Edit it to tune the risk rubric.
|
|
16
|
+
*/
|
|
17
|
+
export const BASE_REVIEW_PROMPT_PATH: string = join(
|
|
18
|
+
import.meta.dirname,
|
|
19
|
+
"prompts",
|
|
20
|
+
"base-review.md"
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
export function readBaseReviewPrompt(): string {
|
|
24
|
+
return readFileSync(BASE_REVIEW_PROMPT_PATH, "utf8").trim();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Compose the review prompt: the always-applied base prompt as the starting
|
|
29
|
+
* block, followed by any matched per-rule prompts in order.
|
|
30
|
+
*
|
|
31
|
+
* The base prompt is always included (even when `rulePrompts` is empty) and is
|
|
32
|
+
* counted toward {@link AGENTIC_OWNERS_MAX_COMPOSED_PROMPT_CHARS}.
|
|
33
|
+
*/
|
|
34
|
+
export function composeReviewPrompt(args: {
|
|
35
|
+
basePrompt: string;
|
|
36
|
+
rulePrompts: readonly string[];
|
|
37
|
+
}): string {
|
|
38
|
+
const parts = [args.basePrompt, ...args.rulePrompts].filter(
|
|
39
|
+
(part) => part !== ""
|
|
40
|
+
);
|
|
41
|
+
const composed = parts.join("\n\n");
|
|
42
|
+
if (composed.length > AGENTIC_OWNERS_MAX_COMPOSED_PROMPT_CHARS) {
|
|
43
|
+
throw new AgenticOwnersConfigValidationError([
|
|
44
|
+
`composed prompt (base + matched rules) is ${composed.length} characters, exceeding the limit of ${AGENTIC_OWNERS_MAX_COMPOSED_PROMPT_CHARS}`,
|
|
45
|
+
]);
|
|
46
|
+
}
|
|
47
|
+
return composed;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function describeRuleForContext(rule: AgenticOwnersPathRule): string {
|
|
51
|
+
const parts: string[] = [`paths=[${rule.paths.join(", ")}]`];
|
|
52
|
+
if (rule.teams.length > 0) {
|
|
53
|
+
parts.push(`teams=[${rule.teams.join(", ")}]`);
|
|
54
|
+
}
|
|
55
|
+
if (rule.users.length > 0) {
|
|
56
|
+
parts.push(`users=[${rule.users.join(", ")}]`);
|
|
57
|
+
}
|
|
58
|
+
if (rule.alwaysApprove) {
|
|
59
|
+
parts.push("always_approve");
|
|
60
|
+
}
|
|
61
|
+
parts.push(`mode=${rule.mode}`);
|
|
62
|
+
if (rule.selfReview) {
|
|
63
|
+
parts.push("self_review");
|
|
64
|
+
}
|
|
65
|
+
return `${rule.sourcePath} (${parts.join(", ")})`;
|
|
66
|
+
}
|