From b816ace1a654b689a63fcf51393adb1a5c0a9cac Mon Sep 17 00:00:00 2001 From: bitloi Date: Tue, 2 Jun 2026 22:10:15 +0200 Subject: [PATCH 1/2] Add policy onboarding handoff --- src/signals/onboarding-pack.ts | 429 ++++++++++++++++++++++++++++++ test/unit/onboarding-pack.test.ts | 237 +++++++++++++++++ 2 files changed, 666 insertions(+) create mode 100644 src/signals/onboarding-pack.ts create mode 100644 test/unit/onboarding-pack.test.ts diff --git a/src/signals/onboarding-pack.ts b/src/signals/onboarding-pack.ts new file mode 100644 index 0000000000..d53db589d1 --- /dev/null +++ b/src/signals/onboarding-pack.ts @@ -0,0 +1,429 @@ +import { isFocusManifestPublicSafe } from "./focus-manifest"; +import { nowIso } from "../utils/json"; + +export type RepoPolicyContributionLane = { + id?: string | null; + title: string; + summary: string; + preferredPaths?: string[]; + discouragedPaths?: string[]; + validationExpectations?: string[]; + publicNotes?: string[]; +}; + +export type RepoPolicyLabelPolicy = { + preferredLabels?: string[]; + requiredLabels?: string[]; + discouragedLabels?: string[]; + note?: string | null; +}; + +export type RepoPolicyCompilerOutput = { + repoFullName: string; + generatedAt?: string | null; + contributionLanes?: RepoPolicyContributionLane[]; + labelPolicy?: RepoPolicyLabelPolicy; + validationExpectations?: string[]; + readinessWarnings?: string[]; + maintainerExpectations?: string[]; + publicOutputBoundaries?: string[]; + privateOwnerContext?: string[]; +}; + +export type RepoOnboardingDroppedPublicItem = { + field: string; + reason: "empty" | "unsafe_public_text"; +}; + +export type RepoOnboardingContributionLane = { + id: string; + title: string; + summary: string; + preferredPaths: string[]; + discouragedPaths: string[]; + validationExpectations: string[]; + publicNotes: string[]; +}; + +export type RepoOnboardingLabelPolicy = { + preferredLabels: string[]; + requiredLabels: string[]; + discouragedLabels: string[]; + note: string | null; +}; + +export type RepoOnboardingPackPreview = { + repoFullName: string; + generatedAt: string; + source: "policy_compiler"; + previewOnly: true; + publicSafe: true; + contributionLanes: RepoOnboardingContributionLane[]; + labelPolicy: RepoOnboardingLabelPolicy; + validationExpectations: string[]; + readinessWarnings: string[]; + maintainerExpectations: string[]; + publicOutputBoundaries: string[]; + previewMarkdown: string; + droppedPublicItems: RepoOnboardingDroppedPublicItem[]; + privateOwnerContext: { + itemCount: number; + includedInPublicPreview: false; + }; + publication: { + status: "preview_only"; + allowed: false; + actions: string[]; + reason: string; + }; +}; + +const DEFAULT_PUBLIC_OUTPUT_BOUNDARIES = [ + "Keep sensitive credentials, account secrets, compensation estimates, private maintainer evidence, and local paths out of public contribution text.", + "Keep the pack as guidance for accepted work, not as automated GitHub action.", +]; + +const DEFAULT_VALIDATION_EXPECTATIONS = [ + "Run the repository test command documented by maintainers before submitting.", +]; + +const DEFAULT_MAINTAINER_EXPECTATIONS = [ + "Keep pull requests small, reviewable, and tied to accepted repository scope.", +]; + +export function buildRepoOnboardingPackPreview( + policyOutput: RepoPolicyCompilerOutput, + options: { generatedAt?: string } = {}, +): RepoOnboardingPackPreview { + const droppedPublicItems: RepoOnboardingDroppedPublicItem[] = []; + const generatedAt = options.generatedAt ?? policyOutput.generatedAt ?? nowIso(); + + const contributionLanes = (policyOutput.contributionLanes ?? []) + .map((lane, index) => sanitizeContributionLane(lane, index, droppedPublicItems)) + .filter((lane): lane is RepoOnboardingContributionLane => lane !== null); + + const labelPolicy = sanitizeLabelPolicy(policyOutput.labelPolicy, droppedPublicItems); + const validationExpectations = safePublicList( + policyOutput.validationExpectations, + "validationExpectations", + droppedPublicItems, + ); + const readinessWarnings = safePublicList( + policyOutput.readinessWarnings, + "readinessWarnings", + droppedPublicItems, + ); + const maintainerExpectations = withDefaultPublicList( + policyOutput.maintainerExpectations, + DEFAULT_MAINTAINER_EXPECTATIONS, + "maintainerExpectations", + droppedPublicItems, + ); + const publicOutputBoundaries = withDefaultPublicList( + policyOutput.publicOutputBoundaries, + DEFAULT_PUBLIC_OUTPUT_BOUNDARIES, + "publicOutputBoundaries", + droppedPublicItems, + ); + const publicValidationExpectations = + validationExpectations.length > 0 + ? validationExpectations + : DEFAULT_VALIDATION_EXPECTATIONS; + + const preview: RepoOnboardingPackPreview = { + repoFullName: policyOutput.repoFullName, + generatedAt, + source: "policy_compiler", + previewOnly: true, + publicSafe: true, + contributionLanes, + labelPolicy, + validationExpectations: publicValidationExpectations, + readinessWarnings, + maintainerExpectations, + publicOutputBoundaries, + previewMarkdown: "", + droppedPublicItems, + privateOwnerContext: { + itemCount: policyOutput.privateOwnerContext?.length ?? 0, + includedInPublicPreview: false, + }, + publication: { + status: "preview_only", + allowed: false, + actions: [], + reason: "Preview only; full export and publication remain outside this handoff.", + }, + }; + + preview.previewMarkdown = buildPreviewMarkdown(preview); + + if (!isRepoOnboardingPackPublicSafe(preview)) { + preview.previewMarkdown = + "Onboarding pack preview is unavailable because public text safety checks failed."; + } + + return preview; +} + +export function isRepoOnboardingPackPublicSafe( + preview: Pick< + RepoOnboardingPackPreview, + | "contributionLanes" + | "labelPolicy" + | "validationExpectations" + | "readinessWarnings" + | "maintainerExpectations" + | "publicOutputBoundaries" + | "previewMarkdown" + | "publication" + >, +): boolean { + const publicValues = [ + preview.previewMarkdown, + preview.publication.reason, + ...preview.contributionLanes.flatMap((lane) => [ + lane.id, + lane.title, + lane.summary, + ...lane.preferredPaths, + ...lane.discouragedPaths, + ...lane.validationExpectations, + ...lane.publicNotes, + ]), + ...preview.labelPolicy.preferredLabels, + ...preview.labelPolicy.requiredLabels, + ...preview.labelPolicy.discouragedLabels, + preview.labelPolicy.note ?? "", + ...preview.validationExpectations, + ...preview.readinessWarnings, + ...preview.maintainerExpectations, + ...preview.publicOutputBoundaries, + ]; + + return publicValues.every(isFocusManifestPublicSafe); +} + +function sanitizeContributionLane( + lane: RepoPolicyContributionLane, + index: number, + droppedPublicItems: RepoOnboardingDroppedPublicItem[], +): RepoOnboardingContributionLane | null { + const title = safePublicText( + lane.title, + `contributionLanes.${index}.title`, + droppedPublicItems, + ); + const summary = safePublicText( + lane.summary, + `contributionLanes.${index}.summary`, + droppedPublicItems, + ); + + if (!title || !summary) { + return null; + } + + const id = + safeOptionalPublicText( + lane.id, + `contributionLanes.${index}.id`, + droppedPublicItems, + ) ?? `lane-${index + 1}`; + + return { + id: normalizeIdentifier(id, index), + title, + summary, + preferredPaths: safePublicList( + lane.preferredPaths, + `contributionLanes.${index}.preferredPaths`, + droppedPublicItems, + ), + discouragedPaths: safePublicList( + lane.discouragedPaths, + `contributionLanes.${index}.discouragedPaths`, + droppedPublicItems, + ), + validationExpectations: safePublicList( + lane.validationExpectations, + `contributionLanes.${index}.validationExpectations`, + droppedPublicItems, + ), + publicNotes: safePublicList( + lane.publicNotes, + `contributionLanes.${index}.publicNotes`, + droppedPublicItems, + ), + }; +} + +function sanitizeLabelPolicy( + labelPolicy: RepoPolicyLabelPolicy | undefined, + droppedPublicItems: RepoOnboardingDroppedPublicItem[], +): RepoOnboardingLabelPolicy { + return { + preferredLabels: safePublicList( + labelPolicy?.preferredLabels, + "labelPolicy.preferredLabels", + droppedPublicItems, + ), + requiredLabels: safePublicList( + labelPolicy?.requiredLabels, + "labelPolicy.requiredLabels", + droppedPublicItems, + ), + discouragedLabels: safePublicList( + labelPolicy?.discouragedLabels, + "labelPolicy.discouragedLabels", + droppedPublicItems, + ), + note: safeOptionalPublicText( + labelPolicy?.note, + "labelPolicy.note", + droppedPublicItems, + ), + }; +} + +function withDefaultPublicList( + values: string[] | undefined, + defaults: string[], + field: string, + droppedPublicItems: RepoOnboardingDroppedPublicItem[], +): string[] { + const safeValues = safePublicList(values, field, droppedPublicItems); + return safeValues.length > 0 ? safeValues : defaults; +} + +function safePublicList( + values: string[] | undefined, + field: string, + droppedPublicItems: RepoOnboardingDroppedPublicItem[], +): string[] { + if (!values) { + return []; + } + + return values + .map((value, index) => + safePublicText(value, `${field}.${index}`, droppedPublicItems), + ) + .filter((value): value is string => value !== null); +} + +function safePublicText( + value: string | null | undefined, + field: string, + droppedPublicItems: RepoOnboardingDroppedPublicItem[], +): string | null { + const normalized = normalizeText(value); + + if (!normalized) { + droppedPublicItems.push({ field, reason: "empty" }); + return null; + } + + if (!isFocusManifestPublicSafe(normalized)) { + droppedPublicItems.push({ field, reason: "unsafe_public_text" }); + return null; + } + + return normalized; +} + +function safeOptionalPublicText( + value: string | null | undefined, + field: string, + droppedPublicItems: RepoOnboardingDroppedPublicItem[], +): string | null { + const normalized = normalizeText(value); + + if (!normalized) { + return null; + } + + if (!isFocusManifestPublicSafe(normalized)) { + droppedPublicItems.push({ field, reason: "unsafe_public_text" }); + return null; + } + + return normalized; +} + +function normalizeText(value: string | null | undefined): string | null { + const normalized = value?.replace(/\s+/g, " ").trim(); + return normalized && normalized.length > 0 ? normalized : null; +} + +function normalizeIdentifier(value: string, index: number): string { + const normalized = value + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, ""); + + return normalized.length > 0 ? normalized : `lane-${index + 1}`; +} + +function buildPreviewMarkdown(preview: RepoOnboardingPackPreview): string { + const lines = [ + `# ${preview.repoFullName} onboarding pack preview`, + "", + "Status: preview only. No GitHub publication is performed.", + "", + "## Contribution lanes", + ]; + + if (preview.contributionLanes.length === 0) { + lines.push("- Maintainer-approved work only."); + } else { + preview.contributionLanes.forEach((lane) => { + lines.push(`- ${lane.title}: ${lane.summary}`); + appendNestedList(lines, "Preferred paths", lane.preferredPaths); + appendNestedList(lines, "Validation", lane.validationExpectations); + appendNestedList(lines, "Notes", lane.publicNotes); + }); + } + + lines.push("", "## Label policy"); + appendFlatList(lines, "Preferred", preview.labelPolicy.preferredLabels); + appendFlatList(lines, "Required", preview.labelPolicy.requiredLabels); + appendFlatList(lines, "Discouraged", preview.labelPolicy.discouragedLabels); + if (preview.labelPolicy.note) { + lines.push(`- Note: ${preview.labelPolicy.note}`); + } + + lines.push("", "## Validation expectations"); + appendFlatList(lines, "Expected", preview.validationExpectations); + + if (preview.readinessWarnings.length > 0) { + lines.push("", "## Readiness warnings"); + appendFlatList(lines, "Warning", preview.readinessWarnings); + } + + lines.push("", "## Maintainer expectations"); + appendFlatList(lines, "Expectation", preview.maintainerExpectations); + + lines.push("", "## Public output boundaries"); + appendFlatList(lines, "Boundary", preview.publicOutputBoundaries); + + return lines.join("\n"); +} + +function appendNestedList(lines: string[], label: string, values: string[]): void { + if (values.length === 0) { + return; + } + + lines.push(` - ${label}: ${values.join(", ")}`); +} + +function appendFlatList(lines: string[], label: string, values: string[]): void { + if (values.length === 0) { + return; + } + + values.forEach((value) => { + lines.push(`- ${label}: ${value}`); + }); +} diff --git a/test/unit/onboarding-pack.test.ts b/test/unit/onboarding-pack.test.ts new file mode 100644 index 0000000000..c38c32e0d6 --- /dev/null +++ b/test/unit/onboarding-pack.test.ts @@ -0,0 +1,237 @@ +import { describe, expect, it } from "vitest"; +import { + buildRepoOnboardingPackPreview, + isRepoOnboardingPackPublicSafe, + type RepoPolicyCompilerOutput, +} from "../../src/signals/onboarding-pack"; + +const FORBIDDEN_PUBLIC_LANGUAGE = + /wallet|hotkey|coldkey|mnemonic|payout|reward estimate|raw trust|trust score|public score|private reviewability|private scoreability|farming/i; + +const POLICY_COMPILER_FIXTURE: RepoPolicyCompilerOutput = { + repoFullName: "JSONbored/gittensory", + generatedAt: "2026-06-02T12:00:00.000Z", + contributionLanes: [ + { + id: "direct-pr-quality", + title: "Direct PR quality lane", + summary: "Small pull requests that improve deterministic repo signals.", + preferredPaths: ["src/signals/", "test/unit/"], + discouragedPaths: ["scripts/release/"], + validationExpectations: ["Run npm run test:ci before submission."], + publicNotes: ["Reference accepted repository scope in the PR description."], + }, + { + id: "label-policy", + title: "Label policy lane", + summary: "Changes that make maintainer-owned labels easier to audit.", + preferredPaths: ["src/api/", "apps/gittensory-ui/src/"], + validationExpectations: ["Include a focused regression test for policy output."], + }, + ], + labelPolicy: { + preferredLabels: ["feature", "settings", "developer-experience"], + requiredLabels: ["maintainer-value"], + discouragedLabels: ["needs-triage"], + note: "Use labels to explain accepted scope, not to promise outcomes.", + }, + validationExpectations: [ + "Run npm run test:ci before publication.", + "Keep fixture output stable for downstream onboarding packs.", + ], + readinessWarnings: [ + "Confirm contribution guidance stays previewable before publication.", + "Keep public material separated from maintainer-only context.", + ], + maintainerExpectations: ["Keep pull requests narrow and tied to accepted repository policy."], + publicOutputBoundaries: [ + "Keep sensitive credentials, account secrets, compensation estimates, private maintainer evidence, and local paths out of public contribution text.", + ], + privateOwnerContext: [ + "Private owner note: only maintainers should see internal calibration context.", + ], +}; + +describe("buildRepoOnboardingPackPreview", () => { + it("cross-links policy compiler output into onboarding pack inputs for issue 248", () => { + const preview = buildRepoOnboardingPackPreview(POLICY_COMPILER_FIXTURE); + + expect(preview).toMatchObject({ + repoFullName: "JSONbored/gittensory", + generatedAt: "2026-06-02T12:00:00.000Z", + source: "policy_compiler", + previewOnly: true, + publicSafe: true, + publication: { + status: "preview_only", + allowed: false, + actions: [], + }, + }); + expect(preview.contributionLanes).toHaveLength(2); + expect(preview.contributionLanes[0]).toMatchObject({ + id: "direct-pr-quality", + title: "Direct PR quality lane", + preferredPaths: ["src/signals/", "test/unit/"], + validationExpectations: ["Run npm run test:ci before submission."], + }); + expect(preview.labelPolicy.preferredLabels).toEqual([ + "feature", + "settings", + "developer-experience", + ]); + expect(preview.validationExpectations).toContain( + "Keep fixture output stable for downstream onboarding packs.", + ); + expect(preview.readinessWarnings).toContain( + "Confirm contribution guidance stays previewable before publication.", + ); + expect(preview.previewMarkdown).toContain("Direct PR quality lane"); + expect(preview.previewMarkdown).toContain("Label policy"); + expect(preview.previewMarkdown).toContain("Validation expectations"); + expect(preview.previewMarkdown).toContain("Readiness warnings"); + }); + + it("keeps private owner context out of public onboarding material", () => { + const preview = buildRepoOnboardingPackPreview({ + ...POLICY_COMPILER_FIXTURE, + privateOwnerContext: [ + "Private reviewability note with wallet, hotkey, raw trust, and farming details.", + ], + }); + + expect(preview.privateOwnerContext).toEqual({ + itemCount: 1, + includedInPublicPreview: false, + }); + expect(preview.previewMarkdown).not.toMatch(/Private reviewability note/i); + expect(JSON.stringify(preview)).not.toMatch(FORBIDDEN_PUBLIC_LANGUAGE); + expect(isRepoOnboardingPackPublicSafe(preview)).toBe(true); + }); + + it("drops unsafe public policy text without echoing the unsafe values", () => { + const preview = buildRepoOnboardingPackPreview({ + repoFullName: "JSONbored/gittensory", + contributionLanes: [ + { + title: "Wallet setup lane", + summary: "Publish hotkey and raw trust score guidance.", + publicNotes: ["Use farming language for public contributors."], + }, + ], + labelPolicy: { + preferredLabels: ["public score estimate"], + note: "Mention reward estimate expectations.", + }, + validationExpectations: ["Run npm run test:ci before submission."], + readinessWarnings: ["Do not leak private scoreability details."], + publicOutputBoundaries: ["No wallet, hotkey, or payout text."], + privateOwnerContext: ["This raw trust context stays private."], + }); + + expect(preview.contributionLanes).toEqual([]); + expect(preview.labelPolicy).toMatchObject({ + preferredLabels: [], + requiredLabels: [], + discouragedLabels: [], + note: null, + }); + expect(preview.readinessWarnings).toEqual([]); + expect(preview.publicOutputBoundaries).toEqual( + expect.arrayContaining([ + expect.stringContaining("sensitive credentials"), + ]), + ); + expect(preview.droppedPublicItems).toEqual( + expect.arrayContaining([ + { field: "contributionLanes.0.title", reason: "unsafe_public_text" }, + { field: "contributionLanes.0.summary", reason: "unsafe_public_text" }, + { field: "labelPolicy.preferredLabels.0", reason: "unsafe_public_text" }, + { field: "labelPolicy.note", reason: "unsafe_public_text" }, + { field: "readinessWarnings.0", reason: "unsafe_public_text" }, + { field: "publicOutputBoundaries.0", reason: "unsafe_public_text" }, + ]), + ); + expect(preview.previewMarkdown).toContain("Maintainer-approved work only."); + expect(JSON.stringify(preview)).not.toMatch(FORBIDDEN_PUBLIC_LANGUAGE); + expect(isRepoOnboardingPackPublicSafe(preview)).toBe(true); + }); + + it("handles blank required and optional fields while keeping preview fallback safe", () => { + const preview = buildRepoOnboardingPackPreview({ + repoFullName: "wallet/repo", + contributionLanes: [ + { + title: " ", + summary: "Empty title should drop this lane.", + }, + { + id: "!!!", + title: "Docs lane", + summary: "Accepted documentation improvements.", + }, + ], + labelPolicy: { note: " " }, + validationExpectations: [], + maintainerExpectations: [], + publicOutputBoundaries: [], + }); + + expect(preview.contributionLanes).toEqual([ + expect.objectContaining({ + id: "lane-2", + title: "Docs lane", + }), + ]); + expect(preview.labelPolicy.note).toBeNull(); + expect(preview.validationExpectations).toEqual( + expect.arrayContaining([ + expect.stringContaining("repository test command"), + ]), + ); + expect(preview.droppedPublicItems).toEqual( + expect.arrayContaining([ + { field: "contributionLanes.0.title", reason: "empty" }, + ]), + ); + expect(preview.previewMarkdown).toBe( + "Onboarding pack preview is unavailable because public text safety checks failed.", + ); + expect(isRepoOnboardingPackPublicSafe(preview)).toBe(true); + }); + + it("uses stable defaults when optional policy sections are omitted", () => { + const preview = buildRepoOnboardingPackPreview( + { + repoFullName: "JSONbored/gittensory", + }, + { generatedAt: "2026-06-02T13:00:00.000Z" }, + ); + + expect(preview.generatedAt).toBe("2026-06-02T13:00:00.000Z"); + expect(preview.contributionLanes).toEqual([]); + expect(preview.previewMarkdown).toContain("Maintainer-approved work only."); + expect(preview.validationExpectations).toEqual( + expect.arrayContaining([ + expect.stringContaining("repository test command"), + ]), + ); + expect(preview.maintainerExpectations).toEqual( + expect.arrayContaining([ + expect.stringContaining("small, reviewable"), + ]), + ); + expect(preview.publicOutputBoundaries).toEqual( + expect.arrayContaining([ + expect.stringContaining("sensitive credentials"), + expect.stringContaining("automated GitHub action"), + ]), + ); + expect(preview.privateOwnerContext).toEqual({ + itemCount: 0, + includedInPublicPreview: false, + }); + expect(preview.droppedPublicItems).toEqual([]); + expect(isRepoOnboardingPackPublicSafe(preview)).toBe(true); + }); +}); From 3ad494c2d41605cd42ec5d6f7fcc3500e4e3a04f Mon Sep 17 00:00:00 2001 From: bitloi Date: Tue, 2 Jun 2026 22:10:26 +0200 Subject: [PATCH 2/2] Refresh MCP UI version fallback --- apps/gittensory-ui/src/lib/mcp-package.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/gittensory-ui/src/lib/mcp-package.ts b/apps/gittensory-ui/src/lib/mcp-package.ts index 749d8fe434..684d56af4e 100644 --- a/apps/gittensory-ui/src/lib/mcp-package.ts +++ b/apps/gittensory-ui/src/lib/mcp-package.ts @@ -6,7 +6,7 @@ export const MCP_PACKAGE_NAME = "@jsonbored/gittensory-mcp"; export const MCP_PACKAGE_ENCODED_NAME = "@jsonbored%2fgittensory-mcp"; export const MCP_PACKAGE_REGISTRY_URL = `https://registry.npmjs.org/${MCP_PACKAGE_ENCODED_NAME}`; export const MCP_PACKAGE_NPM_URL = `https://www.npmjs.com/package/${MCP_PACKAGE_NAME}`; -export const MCP_PACKAGE_KNOWN_LATEST_VERSION = "0.3.0"; +export const MCP_PACKAGE_KNOWN_LATEST_VERSION = "0.4.0"; export const MCP_MINIMUM_SUPPORTED_VERSION = "0.2.0"; export type NpmPackageMetadata = {