diff --git a/apps/gittensory-ui/src/lib/command-reference.ts b/apps/gittensory-ui/src/lib/command-reference.ts index 3b366525c9..9659c69c95 100644 --- a/apps/gittensory-ui/src/lib/command-reference.ts +++ b/apps/gittensory-ui/src/lib/command-reference.ts @@ -1,7 +1,156 @@ // Generated by scripts/gen-command-reference.mjs. Do not edit manually. // Regenerate via `npm run command-reference`. +export const PUBLIC_COMMAND_ENTRIES = [ + { + id: "help", + title: "Gittensory command help", + description: "Show public-safe @gittensory command help.", + }, + { + id: "ask", + title: "Gittensory contribution context Q&A", + description: + "Answer contribution-quality questions from connected cached sources with citations.", + }, + { + id: "preflight", + title: "Gittensory preflight", + description: "Summarize public PR hygiene and validation readiness.", + }, + { + id: "blockers", + title: "Gittensory readiness blockers", + description: "Explain public-safe readiness blockers.", + }, + { + id: "duplicate-check", + title: "Gittensory duplicate & WIP check", + description: "Summarize duplicate and in-progress overlap caution.", + }, + { + id: "miner-context", + title: "Gittensory miner context", + description: "Confirm public Gittensor miner context when available.", + }, + { + id: "next-action", + title: "Gittensory next step", + description: "Suggest the next public-safe action.", + }, + { + id: "reviewability", + title: "Gittensory PR readiness", + description: "Summarize maintainer-friendly PR readiness without private review internals.", + }, + { + id: "repo-fit", + title: "Gittensory repository fit", + description: "Summarize public-safe repository fit signals.", + }, + { + id: "packet", + title: "Gittensory public packet", + description: "Prepare public-safe PR packet guidance.", + }, +] as const; + export const PUBLIC_COMMAND_LIST = "@gittensory help\n@gittensory ask\n@gittensory preflight\n@gittensory blockers\n@gittensory duplicate-check\n@gittensory miner-context\n@gittensory next-action\n@gittensory reviewability\n@gittensory repo-fit\n@gittensory packet"; +export const MAINTAINER_COMMAND_ENTRIES = [ + { + id: "queue-summary", + title: "Gittensory maintainer queue summary", + description: "Post a maintainer-only queue digest from cached GitHub metadata.", + }, + { + id: "confirmed-miners", + title: "Gittensory confirmed-miner PRs", + description: "List open PRs whose authors are confirmed in the official-miner cache.", + }, + { + id: "review-now", + title: "Gittensory review-now queue", + description: "List cached PRs that look ready for maintainer review.", + }, + { + id: "needs-author", + title: "Gittensory needs-author queue", + description: "List cached PRs that need author cleanup before detailed review.", + }, + { + id: "duplicate-clusters", + title: "Gittensory duplicate clusters", + description: "List duplicate or WIP clusters visible from cached GitHub metadata.", + }, + { + id: "burden-forecast", + title: "Gittensory burden forecast", + description: "Project maintainer review load and queue-growth risk from cached metadata.", + }, + { + id: "intake-health", + title: "Gittensory intake health", + description: "Summarize contributor-intake health from cached queue and config signals.", + }, + { + id: "outcome-patterns", + title: "Gittensory outcome patterns", + description: "Summarize what this repo actually merges vs closes from cached PR outcomes.", + }, + { + id: "noise-report", + title: "Gittensory noise report", + description: "Highlight queue noise sources maintainers should triage first.", + }, +] as const; + export const MAINTAINER_COMMAND_LIST = "@gittensory queue-summary\n@gittensory confirmed-miners\n@gittensory review-now\n@gittensory needs-author\n@gittensory duplicate-clusters\n@gittensory burden-forecast\n@gittensory intake-health\n@gittensory outcome-patterns\n@gittensory noise-report"; + +export const ACTION_COMMAND_ENTRIES = [ + { + id: "gate-override", + title: "Gate override", + description: + "Record a maintainer override for this commit's gate check only (does not persist across new commits).", + }, + { + id: "review", + title: "Request review", + description: + "Request an auto-review run on the current PR head (`@gittensory re-review` is an alias).", + }, + { + id: "pause", + title: "Pause auto-review", + description: + "Pause auto-review for this PR with an optional reason; does not change gate enforcement.", + }, + { + id: "resume", + title: "Resume auto-review", + description: + "Resume auto-review for this PR with an optional reason; does not change gate enforcement.", + }, + { + id: "resolve", + title: "Resolve finding", + description: + "Mark a review finding as resolved, optionally naming the finding in trailing text.", + }, + { + id: "configuration", + title: "Show configuration", + description: "Show the effective resolved review configuration for this repository.", + }, + { + id: "explain", + title: "Explain finding", + description: + "Explain a specific review finding; supply the finding reference in trailing text.", + }, +] as const; + +export const ACTION_COMMAND_LIST = + "@gittensory gate-override\n@gittensory review\n@gittensory pause\n@gittensory resume\n@gittensory resolve\n@gittensory configuration\n@gittensory explain"; diff --git a/apps/gittensory-ui/src/routeTree.gen.ts b/apps/gittensory-ui/src/routeTree.gen.ts index 7173d5a8c6..78d409c9f7 100644 --- a/apps/gittensory-ui/src/routeTree.gen.ts +++ b/apps/gittensory-ui/src/routeTree.gen.ts @@ -49,6 +49,7 @@ import { Route as DocsMaintainerWorkflowRouteImport } from './routes/docs.mainta import { Route as DocsMaintainerSelfHostingRouteImport } from './routes/docs.maintainer-self-hosting' import { Route as DocsMaintainerInstallTrustRouteImport } from './routes/docs.maintainer-install-trust' import { Route as DocsHowReviewsWorkRouteImport } from './routes/docs.how-reviews-work' +import { Route as DocsGittensoryCommandsRouteImport } from './routes/docs.gittensory-commands' import { Route as DocsGithubAppRouteImport } from './routes/docs.github-app' import { Route as DocsBranchAnalysisRouteImport } from './routes/docs.branch-analysis' import { Route as DocsBetaOnboardingRouteImport } from './routes/docs.beta-onboarding' @@ -279,6 +280,11 @@ const DocsHowReviewsWorkRoute = DocsHowReviewsWorkRouteImport.update({ path: '/how-reviews-work', getParentRoute: () => DocsRoute, } as any) +const DocsGittensoryCommandsRoute = DocsGittensoryCommandsRouteImport.update({ + id: '/gittensory-commands', + path: '/gittensory-commands', + getParentRoute: () => DocsRoute, +} as any) const DocsGithubAppRoute = DocsGithubAppRouteImport.update({ id: '/github-app', path: '/github-app', @@ -398,6 +404,7 @@ export interface FileRoutesByFullPath { '/docs/beta-onboarding': typeof DocsBetaOnboardingRoute '/docs/branch-analysis': typeof DocsBranchAnalysisRoute '/docs/github-app': typeof DocsGithubAppRoute + '/docs/gittensory-commands': typeof DocsGittensoryCommandsRoute '/docs/how-reviews-work': typeof DocsHowReviewsWorkRoute '/docs/maintainer-install-trust': typeof DocsMaintainerInstallTrustRoute '/docs/maintainer-self-hosting': typeof DocsMaintainerSelfHostingRoute @@ -455,6 +462,7 @@ export interface FileRoutesByTo { '/docs/beta-onboarding': typeof DocsBetaOnboardingRoute '/docs/branch-analysis': typeof DocsBranchAnalysisRoute '/docs/github-app': typeof DocsGithubAppRoute + '/docs/gittensory-commands': typeof DocsGittensoryCommandsRoute '/docs/how-reviews-work': typeof DocsHowReviewsWorkRoute '/docs/maintainer-install-trust': typeof DocsMaintainerInstallTrustRoute '/docs/maintainer-self-hosting': typeof DocsMaintainerSelfHostingRoute @@ -516,6 +524,7 @@ export interface FileRoutesById { '/docs/beta-onboarding': typeof DocsBetaOnboardingRoute '/docs/branch-analysis': typeof DocsBranchAnalysisRoute '/docs/github-app': typeof DocsGithubAppRoute + '/docs/gittensory-commands': typeof DocsGittensoryCommandsRoute '/docs/how-reviews-work': typeof DocsHowReviewsWorkRoute '/docs/maintainer-install-trust': typeof DocsMaintainerInstallTrustRoute '/docs/maintainer-self-hosting': typeof DocsMaintainerSelfHostingRoute @@ -578,6 +587,7 @@ export interface FileRouteTypes { | '/docs/beta-onboarding' | '/docs/branch-analysis' | '/docs/github-app' + | '/docs/gittensory-commands' | '/docs/how-reviews-work' | '/docs/maintainer-install-trust' | '/docs/maintainer-self-hosting' @@ -635,6 +645,7 @@ export interface FileRouteTypes { | '/docs/beta-onboarding' | '/docs/branch-analysis' | '/docs/github-app' + | '/docs/gittensory-commands' | '/docs/how-reviews-work' | '/docs/maintainer-install-trust' | '/docs/maintainer-self-hosting' @@ -695,6 +706,7 @@ export interface FileRouteTypes { | '/docs/beta-onboarding' | '/docs/branch-analysis' | '/docs/github-app' + | '/docs/gittensory-commands' | '/docs/how-reviews-work' | '/docs/maintainer-install-trust' | '/docs/maintainer-self-hosting' @@ -1024,6 +1036,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof DocsHowReviewsWorkRouteImport parentRoute: typeof DocsRoute } + '/docs/gittensory-commands': { + id: '/docs/gittensory-commands' + path: '/gittensory-commands' + fullPath: '/docs/gittensory-commands' + preLoaderRoute: typeof DocsGittensoryCommandsRouteImport + parentRoute: typeof DocsRoute + } '/docs/github-app': { id: '/docs/github-app' path: '/github-app' @@ -1204,6 +1223,7 @@ interface DocsRouteChildren { DocsBetaOnboardingRoute: typeof DocsBetaOnboardingRoute DocsBranchAnalysisRoute: typeof DocsBranchAnalysisRoute DocsGithubAppRoute: typeof DocsGithubAppRoute + DocsGittensoryCommandsRoute: typeof DocsGittensoryCommandsRoute DocsHowReviewsWorkRoute: typeof DocsHowReviewsWorkRoute DocsMaintainerInstallTrustRoute: typeof DocsMaintainerInstallTrustRoute DocsMaintainerSelfHostingRoute: typeof DocsMaintainerSelfHostingRoute @@ -1239,6 +1259,7 @@ const DocsRouteChildren: DocsRouteChildren = { DocsBetaOnboardingRoute: DocsBetaOnboardingRoute, DocsBranchAnalysisRoute: DocsBranchAnalysisRoute, DocsGithubAppRoute: DocsGithubAppRoute, + DocsGittensoryCommandsRoute: DocsGittensoryCommandsRoute, DocsHowReviewsWorkRoute: DocsHowReviewsWorkRoute, DocsMaintainerInstallTrustRoute: DocsMaintainerInstallTrustRoute, DocsMaintainerSelfHostingRoute: DocsMaintainerSelfHostingRoute, diff --git a/apps/gittensory-ui/src/routes/docs.gittensory-commands.tsx b/apps/gittensory-ui/src/routes/docs.gittensory-commands.tsx new file mode 100644 index 0000000000..0891c5f62f --- /dev/null +++ b/apps/gittensory-ui/src/routes/docs.gittensory-commands.tsx @@ -0,0 +1,163 @@ +import { createFileRoute, Link } from "@tanstack/react-router"; + +import { DocsPage } from "@/components/site/docs-page"; +import { Callout, CodeBlock } from "@/components/site/primitives"; +import { + ACTION_COMMAND_ENTRIES, + MAINTAINER_COMMAND_ENTRIES, + PUBLIC_COMMAND_ENTRIES, +} from "@/lib/command-reference"; + +export const Route = createFileRoute("/docs/gittensory-commands")({ + head: () => ({ + meta: [ + { title: "@gittensory command reference — Gittensory docs" }, + { + name: "description", + content: + "Every @gittensory PR and issue comment command: syntax, default authorization roles, and the hard boundary between auto-review and the one-shot gate.", + }, + { property: "og:title", content: "@gittensory command reference — Gittensory docs" }, + { + property: "og:description", + content: + "Every @gittensory PR and issue comment command: syntax, default authorization roles, and the hard boundary between auto-review and the one-shot gate.", + }, + { property: "og:url", content: "/docs/gittensory-commands" }, + ], + links: [{ rel: "canonical", href: "/docs/gittensory-commands" }], + }), + component: GittensoryCommandsReference, +}); + +const DEFAULT_ROLE_SUMMARY: Record = { + help: "maintainer, collaborator, confirmed_miner (default policy)", + ask: "maintainer, collaborator, confirmed_miner", + preflight: "maintainer, collaborator, confirmed_miner", + blockers: "maintainer, collaborator, confirmed_miner", + "duplicate-check": "maintainer, collaborator, confirmed_miner", + "miner-context": "maintainer, collaborator, confirmed_miner", + "next-action": "maintainer, collaborator, confirmed_miner", + reviewability: "maintainer, collaborator, confirmed_miner", + "repo-fit": "maintainer, collaborator, confirmed_miner", + packet: "maintainer, collaborator, confirmed_miner", + "queue-summary": "maintainer, collaborator", + "confirmed-miners": "maintainer, collaborator", + "review-now": "maintainer, collaborator", + "needs-author": "maintainer, collaborator", + "duplicate-clusters": "maintainer, collaborator", + "burden-forecast": "maintainer, collaborator", + "intake-health": "maintainer, collaborator", + "outcome-patterns": "maintainer, collaborator", + "noise-report": "maintainer, collaborator", + "gate-override": "maintainer, collaborator", + review: "maintainer, collaborator, confirmed_miner", + pause: "maintainer, collaborator", + resume: "maintainer, collaborator", + resolve: "maintainer, collaborator", + configuration: "maintainer, collaborator", + explain: "maintainer, collaborator", +}; + +function CommandTable({ + title, + entries, +}: { + title: string; + entries: ReadonlyArray<{ id: string; title: string; description: string }>; +}) { + return ( + <> +

{title}

+
+ + + + + + + + + + {entries.map((entry) => ( + + + + + + ))} + +
SyntaxEffectDefault roles
+ @gittensory {entry.id} + {entry.description} + {DEFAULT_ROLE_SUMMARY[entry.id] ?? "see policy"} +
+
+ + ); +} + +function GittensoryCommandsReference() { + return ( + + + Commands never flip the gate to advisory and never bypass the one-shot disposition.{" "} + pause and resume affect only auto-review scheduling — not gate + enforcement. See How reviews work for the + gate/review split. + + +

Syntax

+

+ Post a comment on a pull request (or issue thread) mentioning @gittensory{" "} + followed by a verb. Trailing free text becomes the command argument where noted (for example{" "} + @gittensory ask what should I fix first?). +

+ [argument or reason]`} /> + + + + + +

Per-command authorization overrides

+

+ Default allowed roles ship in the worker configuration. A maintainer can tighten or widen a + single verb via commandAuthorization in .gittensory.yml (resolved + in the same order as other per-repo settings: manifest → database → defaults). +

+ +

+ Maintainer-only digest verbs ignore a plain pr_author role even when widened — + only maintainer, collaborator, and confirmed_miner survive the clamp for those commands. +

+ +

Related docs

+
    +
  • + Maintainer workflow — when to invoke commands + in a PR thread +
  • +
  • + How reviews work — gate, dual-AI review, and + unified comment +
  • +
  • + Tuning your reviews — per-repo review and agent execution + modes +
  • +
+
+ ); +} diff --git a/apps/gittensory-ui/src/routes/docs.index.tsx b/apps/gittensory-ui/src/routes/docs.index.tsx index f030bf1632..d57d64eeed 100644 --- a/apps/gittensory-ui/src/routes/docs.index.tsx +++ b/apps/gittensory-ui/src/routes/docs.index.tsx @@ -74,6 +74,7 @@ const AUDIENCES: Audience[] = [ { to: "/docs/maintainer-install-trust", label: "Install & trust guide" }, { to: "/docs/github-app", label: "GitHub App configuration" }, { to: "/docs/maintainer-workflow", label: "Maintainer workflow" }, + { to: "/docs/gittensory-commands", label: "@gittensory commands" }, { to: "/docs/self-hosting-rees-analyzers", label: "REES analyzers" }, { to: "/docs/upstream-drift", label: "Upstream drift" }, { to: "/docs/ai-summaries", label: "AI summaries policy" }, diff --git a/apps/gittensory-ui/src/routes/docs.maintainer-workflow.tsx b/apps/gittensory-ui/src/routes/docs.maintainer-workflow.tsx index 2e0eeedbf6..0000ce2f8a 100644 --- a/apps/gittensory-ui/src/routes/docs.maintainer-workflow.tsx +++ b/apps/gittensory-ui/src/routes/docs.maintainer-workflow.tsx @@ -158,6 +158,10 @@ GET /v1/repos/:owner/:repo/registration-readiness`} only:

+

+ For syntax, default roles, PR action verbs, and the gate vs auto-review boundary, see the{" "} + @gittensory command reference. +

Public-facing comments are sanitized before they leave the Worker. Private scoring, reward, diff --git a/scripts/check-docs-drift.mjs b/scripts/check-docs-drift.mjs index df60673f22..c9f6346a5b 100644 --- a/scripts/check-docs-drift.mjs +++ b/scripts/check-docs-drift.mjs @@ -109,7 +109,7 @@ export function checkDocsDrift({ root, readFile = defaultReadFile }) { if (allCommandIds.length < 15) { failures.push(`src/github/commands.ts: extraction found only ${allCommandIds.length} unique @gittensory command ids -- expected 15+; the extraction regex may be broken`); } else { - const commandDocsPages = ["docs.maintainer-workflow.tsx", "docs.maintainer-install-trust.tsx"]; + const commandDocsPages = ["docs.maintainer-workflow.tsx", "docs.maintainer-install-trust.tsx", "docs.gittensory-commands.tsx"]; for (const page of commandDocsPages) { const pageText = read(`${DOCS_ROUTES_DIR}/${page}`); if (pageText.includes("@/lib/command-reference")) continue; diff --git a/scripts/gen-command-reference.d.mts b/scripts/gen-command-reference.d.mts index d7984b4969..990803fb95 100644 --- a/scripts/gen-command-reference.d.mts +++ b/scripts/gen-command-reference.d.mts @@ -24,11 +24,13 @@ export declare function renderCommandList(entries: CommandCatalogEntry[]): strin export declare function collectCommandCatalogs(options?: CommandCatalogOptions): { publicCommands: CommandCatalogEntry[]; maintainerCommands: CommandCatalogEntry[]; + actionCommands: CommandCatalogEntry[]; }; export declare function renderCommandReferenceModule(catalogs: { publicCommands: CommandCatalogEntry[]; maintainerCommands: CommandCatalogEntry[]; + actionCommands: CommandCatalogEntry[]; }): string; export declare function writeCommandReference(options?: WriteCommandReferenceOptions): { @@ -36,4 +38,5 @@ export declare function writeCommandReference(options?: WriteCommandReferenceOpt outputPath: string; publicCommands: CommandCatalogEntry[]; maintainerCommands: CommandCatalogEntry[]; + actionCommands: CommandCatalogEntry[]; }; diff --git a/scripts/gen-command-reference.mjs b/scripts/gen-command-reference.mjs index 23604f34df..27c658f68f 100644 --- a/scripts/gen-command-reference.mjs +++ b/scripts/gen-command-reference.mjs @@ -13,6 +13,7 @@ export const DEFAULT_OUTPUT_PATH = "apps/gittensory-ui/src/lib/command-reference const PUBLIC_CATALOG_NAME = "PUBLIC_MENTION_COMMAND_CATALOG"; const MAINTAINER_CATALOG_NAME = "MAINTAINER_QUEUE_DIGEST_COMMAND_CATALOG"; +const ACTION_CATALOG_NAME = "GITTENSORY_ACTION_COMMAND_CATALOG"; // Self-defense floor: 10 public + 9 maintainer-only commands exist today. If extraction ever finds fewer // than 15 total entries across both catalogs, the extraction regex is almost certainly broken -- fail @@ -43,28 +44,62 @@ export function renderCommandList(entries) { return entries.map((entry) => `@gittensory ${entry.id}`).join("\n"); } +const PRETTIER_PRINT_WIDTH = 100; + +function renderDescriptionField(description) { + const inline = ` description: ${JSON.stringify(description)},`; + if (inline.length <= PRETTIER_PRINT_WIDTH) { + return inline; + } + return ` description:\n ${JSON.stringify(description)},`; +} + +function renderCommandEntries(entries) { + const rows = entries.map( + (entry) => + ` {\n id: ${JSON.stringify(entry.id)},\n title: ${JSON.stringify(entry.title)},\n${renderDescriptionField(entry.description)}\n },`, + ); + return `[\n${rows.join("\n")}\n]`; +} + export function collectCommandCatalogs({ rootDir = process.cwd(), sourcePath = DEFAULT_SOURCE_PATH } = {}) { const sourceText = readFileSync(resolve(rootDir, sourcePath), "utf8"); const publicCommands = extractCatalogEntries(sourceText, PUBLIC_CATALOG_NAME); const maintainerCommands = extractCatalogEntries(sourceText, MAINTAINER_CATALOG_NAME); + const actionCommands = extractCatalogEntries(sourceText, ACTION_CATALOG_NAME); const total = publicCommands.length + maintainerCommands.length; if (total < MIN_TOTAL_COMMANDS) { throw new Error( `gen-command-reference: extraction found only ${total} total @gittensory command(s) across both catalogs in ${sourcePath} -- expected ${MIN_TOTAL_COMMANDS}+; the extraction regex may be broken`, ); } - return { publicCommands, maintainerCommands }; + if (actionCommands.length < 7) { + throw new Error( + `gen-command-reference: extraction found only ${actionCommands.length} PR action command(s) in ${sourcePath} -- expected 7; the extraction regex may be broken`, + ); + } + return { publicCommands, maintainerCommands, actionCommands }; } -export function renderCommandReferenceModule({ publicCommands, maintainerCommands }) { - return `// Generated by scripts/gen-command-reference.mjs. Do not edit manually. +export function renderCommandReferenceModule({ publicCommands, maintainerCommands, actionCommands }) { + const body = `// Generated by scripts/gen-command-reference.mjs. Do not edit manually. // Regenerate via \`npm run command-reference\`. +export const PUBLIC_COMMAND_ENTRIES = ${renderCommandEntries(publicCommands)} as const; + export const PUBLIC_COMMAND_LIST = ${JSON.stringify(renderCommandList(publicCommands))}; +export const MAINTAINER_COMMAND_ENTRIES = ${renderCommandEntries(maintainerCommands)} as const; + export const MAINTAINER_COMMAND_LIST = ${JSON.stringify(renderCommandList(maintainerCommands))}; + +export const ACTION_COMMAND_ENTRIES = ${renderCommandEntries(actionCommands)} as const; + +export const ACTION_COMMAND_LIST = + ${JSON.stringify(renderCommandList(actionCommands))}; `; + return body; } export function writeCommandReference({ @@ -73,8 +108,8 @@ export function writeCommandReference({ outputPath = DEFAULT_OUTPUT_PATH, check = false, } = {}) { - const { publicCommands, maintainerCommands } = collectCommandCatalogs({ rootDir, sourcePath }); - const output = renderCommandReferenceModule({ publicCommands, maintainerCommands }); + const { publicCommands, maintainerCommands, actionCommands } = collectCommandCatalogs({ rootDir, sourcePath }); + const output = renderCommandReferenceModule({ publicCommands, maintainerCommands, actionCommands }); const absOutput = resolve(rootDir, outputPath); const current = existsSync(absOutput) ? readFileSync(absOutput, "utf8") : null; const changed = current !== output; @@ -82,7 +117,7 @@ export function writeCommandReference({ mkdirSync(dirname(absOutput), { recursive: true }); writeFileSync(absOutput, output); } - return { changed, outputPath, publicCommands, maintainerCommands }; + return { changed, outputPath, publicCommands, maintainerCommands, actionCommands }; } function main(argv) { @@ -93,7 +128,7 @@ function main(argv) { process.exit(1); } process.stdout.write( - `gen-command-reference: ${check ? "checked" : "wrote"} ${result.publicCommands.length} public + ${result.maintainerCommands.length} maintainer-only command references in ${result.outputPath}\n`, + `gen-command-reference: ${check ? "checked" : "wrote"} ${result.publicCommands.length} public + ${result.maintainerCommands.length} maintainer-only + ${result.actionCommands.length} action command references in ${result.outputPath}\n`, ); } diff --git a/src/github/commands.ts b/src/github/commands.ts index e8c4bcff7f..5888db0d22 100644 --- a/src/github/commands.ts +++ b/src/github/commands.ts @@ -4,7 +4,7 @@ import { suggestCommand as suggestCommandFromCatalog, type CommandSuggestCatalog, } from "./command-suggest"; -import { gittensoryFooter } from "./footer"; +import { gittensoryFooter, GITTENSORY_SITE_URL } from "./footer"; import type { AgentRunBundle } from "../services/agent-orchestrator"; import type { GittensorContributorSnapshot, OfficialGittensorMinerDetection } from "../gittensor/api"; import type { AgentActionRecord, RepositoryCommandAuthorizationPolicy } from "../types"; @@ -780,6 +780,8 @@ function helpSections(unknownVerb?: string | undefined): string[] { "- `@gittensory noise-report` highlights queue noise to triage first.", "", ...actionCommandHelpSections(), + "", + `- Full command reference (syntax, roles, gate boundary): ${GITTENSORY_SITE_URL}/docs/gittensory-commands`, ]; } diff --git a/test/unit/check-docs-drift-script.test.ts b/test/unit/check-docs-drift-script.test.ts index bad6e4d296..7573acf6f4 100644 --- a/test/unit/check-docs-drift-script.test.ts +++ b/test/unit/check-docs-drift-script.test.ts @@ -120,6 +120,8 @@ describe("check-docs-drift script", () => { "apps/gittensory-ui/src/routes/docs.privacy-security.tsx": buildFlagsPageText(baseFlagNames), "apps/gittensory-ui/src/routes/docs.maintainer-workflow.tsx": buildDocsPageText(allBaseCommandIds), "apps/gittensory-ui/src/routes/docs.maintainer-install-trust.tsx": buildDocsPageText(allBaseCommandIds), + "apps/gittensory-ui/src/routes/docs.gittensory-commands.tsx": + 'import { PUBLIC_COMMAND_ENTRIES, MAINTAINER_COMMAND_ENTRIES, ACTION_COMMAND_ENTRIES } from "@/lib/command-reference";', "apps/gittensory-ui/src/routes/docs.how-reviews-work.tsx": buildGateModePageText(), "apps/gittensory-ui/src/routes/docs.github-app.tsx": buildGateModePageText(), }; diff --git a/test/unit/gen-command-reference-script.test.ts b/test/unit/gen-command-reference-script.test.ts index af8b1717bb..39cedeccaf 100644 --- a/test/unit/gen-command-reference-script.test.ts +++ b/test/unit/gen-command-reference-script.test.ts @@ -75,6 +75,7 @@ describe("gen-command-reference script (#3046)", () => { it("extracts a fixture with exactly 15 total commands without throwing", () => { const publicIds = Array.from({ length: 10 }, (_, i) => `{ id: "public-${i}", title: "Public ${i}", description: "Desc ${i}." },`).join("\n"); const maintainerIds = Array.from({ length: 5 }, (_, i) => `{ id: "maint-${i}", title: "Maint ${i}", description: "Desc ${i}." },`).join("\n"); + const actionIds = Array.from({ length: 7 }, (_, i) => `{ id: "action-${i}", title: "Action ${i}", description: "Desc ${i}." },`).join("\n"); const root = fixtureRoot(` const PUBLIC_MENTION_COMMAND_CATALOG = [ ${publicIds} @@ -82,18 +83,23 @@ describe("gen-command-reference script (#3046)", () => { const MAINTAINER_QUEUE_DIGEST_COMMAND_CATALOG = [ ${maintainerIds} ] as const; + const GITTENSORY_ACTION_COMMAND_CATALOG = [ + ${actionIds} + ] as const; `); - const { publicCommands, maintainerCommands } = collectCommandCatalogs({ rootDir: root }); + const { publicCommands, maintainerCommands, actionCommands } = collectCommandCatalogs({ rootDir: root }); expect(publicCommands).toHaveLength(10); expect(maintainerCommands).toHaveLength(5); + expect(actionCommands).toHaveLength(7); }); - it("extracts the real 10 public + 9 maintainer-only commands from the real repo source", () => { - const { publicCommands, maintainerCommands } = collectCommandCatalogs({ rootDir: process.cwd() }); + it("extracts the real 10 public + 9 maintainer-only + 7 action commands from the real repo source", () => { + const { publicCommands, maintainerCommands, actionCommands } = collectCommandCatalogs({ rootDir: process.cwd() }); expect(publicCommands).toHaveLength(10); expect(maintainerCommands).toHaveLength(9); + expect(actionCommands).toHaveLength(7); expect(publicCommands.map((c: CommandCatalogEntry) => c.id)).toEqual([ "help", "ask", @@ -121,16 +127,19 @@ describe("gen-command-reference script (#3046)", () => { }); describe("renderCommandReferenceModule", () => { - it("renders a generated-file header plus both exported string constants", () => { + it("renders a generated-file header plus exported string constants", () => { const output = renderCommandReferenceModule({ publicCommands: [{ id: "help", title: "Help", description: "Show help." }], maintainerCommands: [{ id: "queue-summary", title: "Queue summary", description: "Post a digest." }], + actionCommands: [{ id: "review", title: "Review", description: "Request review." }], }); expect(output).toContain("// Generated by scripts/gen-command-reference.mjs. Do not edit manually."); expect(output).toContain("npm run command-reference"); expect(output).toContain('export const PUBLIC_COMMAND_LIST =\n "@gittensory help";'); expect(output).toContain('export const MAINTAINER_COMMAND_LIST =\n "@gittensory queue-summary";'); + expect(output).toContain('export const ACTION_COMMAND_LIST =\n "@gittensory review";'); + expect(output).toContain("export const ACTION_COMMAND_ENTRIES"); }); }); @@ -141,8 +150,8 @@ describe("gen-command-reference script (#3046)", () => { // generated file must not be hand-edited to make this test pass. it("the committed generated file matches what the generator would produce right now (regression guard)", () => { const rootDir = process.cwd(); - const { publicCommands, maintainerCommands } = collectCommandCatalogs({ rootDir }); - const expected = renderCommandReferenceModule({ publicCommands, maintainerCommands }); + const { publicCommands, maintainerCommands, actionCommands } = collectCommandCatalogs({ rootDir }); + const expected = renderCommandReferenceModule({ publicCommands, maintainerCommands, actionCommands }); const actual = readFileSync(resolve(rootDir, DEFAULT_OUTPUT_PATH), "utf8"); @@ -152,6 +161,6 @@ describe("gen-command-reference script (#3046)", () => { it("prints a clean summary and exits 0 for the real repo state when run as a subprocess with --check", () => { const output = execFileSync("node", ["scripts/gen-command-reference.mjs", "--check"], { encoding: "utf8" }); - expect(output).toMatch(/gen-command-reference: checked \d+ public \+ \d+ maintainer-only command references in/); + expect(output).toMatch(/gen-command-reference: checked \d+ public \+ \d+ maintainer-only \+ \d+ action command references in/); }); }); diff --git a/test/unit/github-commands.test.ts b/test/unit/github-commands.test.ts index e30cbf3f82..c2c059ecab 100644 --- a/test/unit/github-commands.test.ts +++ b/test/unit/github-commands.test.ts @@ -174,6 +174,12 @@ describe("GitHub mention commands", () => { expect(helpCard).toContain("Additional safe details"); }); + it("helpSections links to the public command reference doc (#2171)", () => { + const body = githubCommandsInternals.helpSections().join("\n"); + expect(body).toContain("https://gittensory.aethereal.dev/docs/gittensory-commands"); + expect(body).toContain("Full command reference"); + }); + it("isGittensoryActionCommand distinguishes action verbs from Q&A commands", () => { for (const action of ["gate-override", "review", "pause", "resume", "resolve", "configuration", "explain"] as const) { expect(isGittensoryActionCommand(action)).toBe(true);