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
2,918 changes: 2,918 additions & 0 deletions packages/gittensory-engine/src/focus-manifest.ts

Large diffs are not rendered by default.

62 changes: 62 additions & 0 deletions packages/gittensory-engine/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,65 @@ export {
type PredictedGateInput,
type PredictedGateVerdict,
} from "./predicted-gate.js";
// Focus-manifest parse/compile core (#2280): shared by the maintainer review stack and the miner's
// `.gittensory-miner.yml` goal-spec parser (see miner-goal-spec.ts for the parallel surface).
export {
compileFocusManifestPolicy,
contentLaneConfigToJson,
featuresConfigToJson,
formatManifestValidationNotice,
gateConfigToJson,
isFocusManifestPublicSafe,
matchesManifestPath,
normalizeReadinessGateMode,
parseFocusManifest,
parseFocusManifestContent,
repoDocGenerationConfigToJson,
reviewConfigToJson,
reviewRecapConfigToJson,
settingsOverrideToJson,
MAX_FOCUS_MANIFEST_BYTES,
CONVERGED_FEATURE_KEYS,
COMMENT_VERBOSITY_LEVELS,
EMPTY_AUTO_REVIEW_CONFIG,
EMPTY_MAX_FINDINGS_CONFIG,
EMPTY_SELF_HOST_AI_MODEL_CONFIG,
EMPTY_VISUAL_CONFIG,
LINKED_ISSUE_SATISFACTION_MODES,
REVIEW_FIELD_KEYS,
REVIEW_FINDING_SEVERITY_LADDER,
REVIEW_PROFILES,
type AutoReviewConfig,
type CommentVerbosity,
type ConvergedFeatureKey,
type FocusManifest,
type FocusManifestContentLaneConfig,
type FocusManifestFeaturesConfig,
type FocusManifestGateConfig,
type FocusManifestIssueDiscoveryPolicy,
type FocusManifestLanePreference,
type FocusManifestLinkedIssuePolicy,
type FocusManifestPolicy,
type FocusManifestPolicyContributionLane,
type FocusManifestPolicyLabelPolicy,
type FocusManifestPolicyValidation,
type FocusManifestRepoDocGenerationConfig,
type FocusManifestRepoDocGenerationScope,
type FocusManifestReviewConfig,
type FocusManifestReviewRecapConfig,
type FocusManifestSettings,
type FocusManifestSource,
type LabelingRule,
type LinkedIssueSatisfactionMode,
type MaxFindingsConfig,
type PreMergeCheck,
type ReviewFieldKey,
type ReviewFindingSeverity,
type ReviewPathInstruction,
type ReviewProfile,
type SelfHostAiModelConfig,
type VisualConfig,
type VisualPreviewConfig,
type VisualRoutesConfig,
type VisualTheme,
} from "./focus-manifest.js";
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Canonical REES enrichment-analyzer name registry (#2050). The single source of truth for the analyzer keys that
// both the operator `REES_ANALYZERS` env list and the per-repo `.gittensory.yml` `review.enrichment` toggles are
// validated against. A leaf module with no imports, so the review wiring and the signals-layer manifest parser can
// share it without a heavy or circular dependency.

export const REES_ANALYZER_NAMES = [
"dependency",
"dependencyDiff",
"lockfileDrift",
"secret",
"license",
"installScript",
"heavyDependency",
"hardcodedUrl",
"actionPin",
"eol",
"redos",
"provenance",
"codeowners",
"secretLog",
"assetWeight",
"typosquat",
"commitSignature",
"iacMisconfig",
"nativeBuild",
"history",
"docCommentDrift",
"duplication",
"churnHotspot",
"blameLink",
"approvalIntegrity",
"ciCheckSignals",
"undocumentedExport",
"staleBranch",
"commitHygiene",
"pendingReviewRequests",
"testRatio",
"migrationSafety",
"looseRange",
"terminology",
"todoMarker",
"magicNumber",
"conflictMarker",
"debugLeftover",
"sizeSmell",
"floatingPromise",
"deepNesting",
"errorSwallow",
"unsafeAny",
"a11y",
"i18n",
"unusedExport",
"exhaustiveness",
"flakyTest",
"commitLint",
"apiBreak",
"deprecatedDep",
"revertRecurrence",
"coverageDelta",
"callerImpact",
] as const;

export type ReesAnalyzerName = (typeof REES_ANALYZER_NAMES)[number];

export const REES_ANALYZER_NAME_SET: ReadonlySet<string> = new Set<string>(REES_ANALYZER_NAMES);
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import type { LinkedIssueHardRulesConfig, LinkedIssueHardRulesMode } from "../types/manifest-deps-types.js";

const VALID_LINKED_ISSUE_HARD_RULE_MODES: readonly LinkedIssueHardRulesMode[] = ["block", "off"];
const DEFAULT_CLOSE_DELAY_SECONDS = 30;
const MAX_CLOSE_DELAY_SECONDS = 300;

export const DEFAULT_LINKED_ISSUE_HARD_RULES: LinkedIssueHardRulesConfig = {
ownerAssignedClose: "off",
assignedIssueClose: "off",
missingPointLabelClose: "off",
maintainerOnlyLabelClose: "off",
pointBearingLabels: [],
maintainerOnlyLabels: [],
defaultLabelRepo: false,
verifyBeforeClose: true,
closeDelaySeconds: DEFAULT_CLOSE_DELAY_SECONDS,
};

export function isLinkedIssueHardRuleMode(value: unknown): value is LinkedIssueHardRulesMode {
return typeof value === "string" && (VALID_LINKED_ISSUE_HARD_RULE_MODES as readonly string[]).includes(value);
}

function normalizeStringList(value: unknown, field: string, warnings: string[]): string[] {
if (value === undefined) return [];
if (!Array.isArray(value)) {
warnings.push(`settings.linkedIssueHardRules.${field} must be an array; using no labels.`);
return [];
}
const labels: string[] = [];
for (const [index, item] of value.entries()) {
if (typeof item !== "string" || item.trim().length === 0) {
warnings.push(`settings.linkedIssueHardRules.${field}[${index}] must be a non-empty string; ignoring it.`);
continue;
}
labels.push(item.trim());
}
return labels;
}

function normalizeMode(
value: unknown,
field: "ownerAssignedClose" | "assignedIssueClose" | "missingPointLabelClose" | "maintainerOnlyLabelClose",
warnings: string[],
): LinkedIssueHardRulesMode {
if (value === undefined) return DEFAULT_LINKED_ISSUE_HARD_RULES[field];
if (isLinkedIssueHardRuleMode(value)) return value;
warnings.push(`settings.linkedIssueHardRules.${field} must be one of block, off; using the default "${DEFAULT_LINKED_ISSUE_HARD_RULES[field]}".`);
return DEFAULT_LINKED_ISSUE_HARD_RULES[field];
}

function normalizeBoolean(value: unknown, field: "defaultLabelRepo" | "verifyBeforeClose", warnings: string[]): boolean {
if (value === undefined) return DEFAULT_LINKED_ISSUE_HARD_RULES[field];
if (typeof value === "boolean") return value;
warnings.push(`settings.linkedIssueHardRules.${field} must be a boolean; using the default "${DEFAULT_LINKED_ISSUE_HARD_RULES[field]}".`);
return DEFAULT_LINKED_ISSUE_HARD_RULES[field];
}

function normalizeCloseDelaySeconds(value: unknown, warnings: string[]): number {
if (value === undefined) return DEFAULT_LINKED_ISSUE_HARD_RULES.closeDelaySeconds;
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
warnings.push(`settings.linkedIssueHardRules.closeDelaySeconds must be a non-negative number; using the default "${DEFAULT_CLOSE_DELAY_SECONDS}".`);
return DEFAULT_CLOSE_DELAY_SECONDS;
}
return Math.min(MAX_CLOSE_DELAY_SECONDS, Math.floor(value));
}

export function normalizeLinkedIssueHardRulesConfig(input: unknown, warnings: string[]): LinkedIssueHardRulesConfig {
if (input === undefined) return { ...DEFAULT_LINKED_ISSUE_HARD_RULES, pointBearingLabels: [], maintainerOnlyLabels: [] };
if (typeof input !== "object" || input === null || Array.isArray(input)) {
warnings.push("settings.linkedIssueHardRules must be an object; using the default all-off policy.");
return { ...DEFAULT_LINKED_ISSUE_HARD_RULES, pointBearingLabels: [], maintainerOnlyLabels: [] };
}
const record = input as Record<string, unknown>;
return {
ownerAssignedClose: normalizeMode(record.ownerAssignedClose, "ownerAssignedClose", warnings),
assignedIssueClose: normalizeMode(record.assignedIssueClose, "assignedIssueClose", warnings),
missingPointLabelClose: normalizeMode(record.missingPointLabelClose, "missingPointLabelClose", warnings),
maintainerOnlyLabelClose: normalizeMode(record.maintainerOnlyLabelClose, "maintainerOnlyLabelClose", warnings),
pointBearingLabels: normalizeStringList(record.pointBearingLabels, "pointBearingLabels", warnings),
maintainerOnlyLabels: normalizeStringList(record.maintainerOnlyLabels, "maintainerOnlyLabels", warnings),
defaultLabelRepo: normalizeBoolean(record.defaultLabelRepo, "defaultLabelRepo", warnings),
verifyBeforeClose: normalizeBoolean(record.verifyBeforeClose, "verifyBeforeClose", warnings),
closeDelaySeconds: normalizeCloseDelaySeconds(record.closeDelaySeconds, warnings),
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import type { LinkedIssueLabelPropagationConfig, LinkedIssueLabelPropagationMapping, LinkedIssueLabelPropagationMode } from "../types/manifest-deps-types.js";

export type { LinkedIssueLabelPropagationConfig, LinkedIssueLabelPropagationMapping, LinkedIssueLabelPropagationMode } from "../types/manifest-deps-types.js";

// Linked-issue label PROPAGATION (#priority-linked-issue-gate). Generic, config-driven mechanism: when a
// linked/closing issue already carries a configured label, copy a mapped label onto the PR. Built specifically
// so a maintainer-reward/bonus label (e.g. `gittensor:priority`) can NEVER be inferred from a PR's title,
// changed files, AI output, or existing PR labels — only ever from a linked issue that ALREADY carries it.
// Generic beyond that one use case: any self-hoster can map any issue label to any PR label, exclusive
// (replaces the normal bug/feature type label, like priority does) or additive (applied alongside it).
//
// PURE config types + normalizer only — no GitHub/fetch/Env-dependent imports. `focus-manifest.ts`'s YAML
// parser imports this module directly, and `focus-manifest.ts` is itself pulled into the gittensory-ui
// workspace's isolated typecheck (via `apps/gittensory-ui/src/lib/registration-workspace.ts`), which has no
// visibility into the Worker's ambient `Env` type. The actual GitHub fetch orchestrator
// (`fetchLinkedIssueLabelsForPropagation`) lives in `linked-issue-label-propagation-fetch.ts` instead, kept
// out of this file specifically so the UI workspace's typecheck never has to resolve `Env`.

// Fail-SAFE default: propagation OFF, no mappings. A self-hoster must explicitly opt in per repo.
export const DEFAULT_LINKED_ISSUE_LABEL_PROPAGATION: LinkedIssueLabelPropagationConfig = {
enabled: false,
mode: "exclusive_type_label",
mappings: [],
};

// Exported so `focus-manifest.ts`'s sparse-override parser can check whether a raw `mode` value is
// actually valid before deciding to copy the normalizer's (possibly defaults-filled-on-invalid) result.
export const VALID_LINKED_ISSUE_LABEL_PROPAGATION_MODES: readonly LinkedIssueLabelPropagationMode[] = ["exclusive_type_label"];

function normalizeMapping(input: unknown, index: number, warnings: string[]): LinkedIssueLabelPropagationMapping | null {
if (typeof input !== "object" || input === null || Array.isArray(input)) {
warnings.push(`settings.linkedIssueLabelPropagation.mappings[${index}] must be an object; ignoring it.`);
return null;
}
const record = input as Record<string, unknown>;
const issueLabel = typeof record.issueLabel === "string" ? record.issueLabel.trim() : "";
const prLabel = typeof record.prLabel === "string" ? record.prLabel.trim() : "";
if (issueLabel.length === 0 || prLabel.length === 0) {
warnings.push(`settings.linkedIssueLabelPropagation.mappings[${index}] must have non-empty "issueLabel" and "prLabel" strings; ignoring it.`);
return null;
}
// `removeOtherTypeLabels` picks exclusive (replaces the type label, the gittensor:priority case) vs.
// additive (applied alongside it) -- silently coercing a present-but-wrong-shaped value (e.g. a quoted
// `"true"` string) to `false` could flip an intended-exclusive mapping to additive without any signal,
// so a present, non-boolean value drops the whole entry with a warning instead (omitted is still a
// normal, unwarned default of `false`).
if (record.removeOtherTypeLabels !== undefined && typeof record.removeOtherTypeLabels !== "boolean") {
warnings.push(`settings.linkedIssueLabelPropagation.mappings[${index}].removeOtherTypeLabels must be a boolean; ignoring this mapping.`);
return null;
}
return { issueLabel, prLabel, removeOtherTypeLabels: record.removeOtherTypeLabels === true };
}

/** Defaults-fill a per-repo `linkedIssueLabelPropagation` override into an always-complete, safe config —
* mirrors `normalizeCommandAuthorizationPolicy`'s defaults-fill pattern
* (`src/settings/command-authorization.ts`). Malformed mapping entries are dropped with a warning; valid
* entries in the same array are kept (matches `commandAuthorization`'s per-entry `commands` validation). */
export function normalizeLinkedIssueLabelPropagationConfig(input: unknown, warnings: string[]): LinkedIssueLabelPropagationConfig {
if (input === undefined) return { ...DEFAULT_LINKED_ISSUE_LABEL_PROPAGATION, mappings: [] };
if (typeof input !== "object" || input === null || Array.isArray(input)) {
warnings.push("settings.linkedIssueLabelPropagation must be an object; propagation stays disabled.");
return { ...DEFAULT_LINKED_ISSUE_LABEL_PROPAGATION, mappings: [] };
}
const record = input as Record<string, unknown>;
let enabled = DEFAULT_LINKED_ISSUE_LABEL_PROPAGATION.enabled;
if (record.enabled !== undefined) {
if (typeof record.enabled === "boolean") {
enabled = record.enabled;
} else {
warnings.push(`settings.linkedIssueLabelPropagation.enabled must be a boolean; using the default "${DEFAULT_LINKED_ISSUE_LABEL_PROPAGATION.enabled}".`);
}
}
let mode: LinkedIssueLabelPropagationMode = DEFAULT_LINKED_ISSUE_LABEL_PROPAGATION.mode;
if (record.mode !== undefined) {
if (typeof record.mode === "string" && (VALID_LINKED_ISSUE_LABEL_PROPAGATION_MODES as readonly string[]).includes(record.mode)) {
mode = record.mode as LinkedIssueLabelPropagationMode;
} else {
warnings.push(`settings.linkedIssueLabelPropagation.mode must be one of ${VALID_LINKED_ISSUE_LABEL_PROPAGATION_MODES.join(", ")}; using the default "${DEFAULT_LINKED_ISSUE_LABEL_PROPAGATION.mode}".`);
}
}
let mappings: LinkedIssueLabelPropagationMapping[] = [];
if (record.mappings !== undefined) {
if (Array.isArray(record.mappings)) {
mappings = record.mappings.flatMap((entry, index) => {
const normalized = normalizeMapping(entry, index, warnings);
return normalized ? [normalized] : [];
});
} else {
warnings.push("settings.linkedIssueLabelPropagation.mappings must be an array; using no mappings.");
}
}
return { enabled, mode, mappings };
}
Loading