+ {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);