gh-inari 0.5.2 → 0.6.0
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/README.md +84 -45
- package/dist/artifact.d.ts +1 -1
- package/dist/artifact.js.map +1 -1
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +330 -101
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/reconciliation.d.ts +65 -0
- package/dist/reconciliation.js +274 -0
- package/dist/reconciliation.js.map +1 -0
- package/package.json +7 -2
package/dist/cli.js
CHANGED
|
@@ -3,11 +3,12 @@ import { readFile, stat } from "node:fs/promises";
|
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { ArtifactInputError, parseArtifactInputDocument, prepareIssueArtifact, preparePullRequestArtifact, projectExistingArtifact, renderIssueArtifact, renderPullRequestArtifact,
|
|
6
|
+
import { ArtifactInputError, parseArtifactInputDocument, prepareIssueArtifact, preparePullRequestArtifact, projectExistingArtifact, renderIssueArtifact, renderPullRequestArtifact, } from "./artifact.js";
|
|
7
7
|
import { projectContract, SemanticValidationError, validateSemanticInput, } from "./contract/index.js";
|
|
8
8
|
import { GitHubAdapter, isGitHubAdapterError } from "./github/index.js";
|
|
9
|
-
import { compileLocalGovernedContract, compileRepositoryGovernedContract,
|
|
9
|
+
import { compileLocalGovernedContract, compileRepositoryGovernedContract, createGovernedIssue, createGovernedPullRequest, discoverRepositoryTemplates, rejectGovernedPolicyOverride, } from "./governance.js";
|
|
10
10
|
import { discoverTemplates } from "./template-discovery.js";
|
|
11
|
+
import { applySemanticPatch, assessExistingArtifact, currentArtifactInput, diffArtifact, prepareRemediationArtifact, prepareSyncInput, readGovernedExistingArtifact, RemediationError, updateGovernedExistingArtifact, } from "./reconciliation.js";
|
|
11
12
|
import { discoverSemanticTemplates, importNativeTemplate, renderSemanticCompactSchema, syncSemanticTemplates, SEMANTIC_ISSUE_DIRECTORY, SEMANTIC_PULL_REQUEST_FILE, SEMANTIC_TEMPLATE_DIRECTORY, } from "./semantic-template.js";
|
|
12
13
|
const EXIT_USAGE = 1;
|
|
13
14
|
const EXIT_VALIDATION = 2;
|
|
@@ -21,6 +22,19 @@ const RUNTIME_CAPABILITIES = [
|
|
|
21
22
|
"extension-bootstrap",
|
|
22
23
|
];
|
|
23
24
|
const CANONICAL_INVOCATION = "gh inari";
|
|
25
|
+
const KNOWN_ARTIFACT_COMMANDS = new Set([
|
|
26
|
+
"schema",
|
|
27
|
+
"validate",
|
|
28
|
+
"render",
|
|
29
|
+
"create",
|
|
30
|
+
"explain",
|
|
31
|
+
"get",
|
|
32
|
+
"check",
|
|
33
|
+
"edit",
|
|
34
|
+
"normalize",
|
|
35
|
+
"sync",
|
|
36
|
+
]);
|
|
37
|
+
const KNOWN_TEMPLATE_COMMANDS = new Set(["list", "sync", "import"]);
|
|
24
38
|
const INSTALL_COMMAND = "gh extension install yohn-jp/gh-inari";
|
|
25
39
|
const UPDATE_COMMAND = "gh extension upgrade inari";
|
|
26
40
|
const FALLBACK_COMMAND = "npx --yes gh-inari";
|
|
@@ -34,6 +48,7 @@ const BOOLEAN_OPTIONS = new Set([
|
|
|
34
48
|
"maintainerCanModify",
|
|
35
49
|
"compact",
|
|
36
50
|
"check",
|
|
51
|
+
"dryRun",
|
|
37
52
|
]);
|
|
38
53
|
const VALUE_OPTIONS = new Set([
|
|
39
54
|
"from",
|
|
@@ -50,6 +65,8 @@ const VALUE_OPTIONS = new Set([
|
|
|
50
65
|
/** The installed gh-inari executable entrypoint. */
|
|
51
66
|
export async function runCli(argv, dependencies = {}) {
|
|
52
67
|
const metadata = dependencies.packageMetadata ?? readPackageMetadata();
|
|
68
|
+
if (!isOwnedInvocation(argv))
|
|
69
|
+
return runGhFallback(argv, dependencies);
|
|
53
70
|
let parsed;
|
|
54
71
|
try {
|
|
55
72
|
parsed = parseArguments(argv);
|
|
@@ -68,9 +85,10 @@ export async function runCli(argv, dependencies = {}) {
|
|
|
68
85
|
parsed.positionals[0] === "diagnose" ||
|
|
69
86
|
parsed.positionals[0] === "doctor";
|
|
70
87
|
const versionRequested = parsed.options.version === true || parsed.positionals[0] === "version";
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
88
|
+
const helpRequested = parsed.options.help !== undefined && parsed.options.help !== false;
|
|
89
|
+
if (helpRequested || (parsed.positionals.length === 0 && !versionRequested && !diagnosticRequested)) {
|
|
90
|
+
printHelpFor(parsed.positionals, parsed.options.help);
|
|
91
|
+
return parsed.positionals.length === 0 && !helpRequested ? EXIT_USAGE : 0;
|
|
74
92
|
}
|
|
75
93
|
const json = parsed.options.json === true;
|
|
76
94
|
try {
|
|
@@ -280,6 +298,17 @@ function runGhDiagnosticCommand(args) {
|
|
|
280
298
|
};
|
|
281
299
|
}
|
|
282
300
|
}
|
|
301
|
+
/** Delegates argv gh-inari does not own to the real `gh` binary, so `gh inari` is a strict superset of `gh`. */
|
|
302
|
+
function runGhFallback(argv, dependencies) {
|
|
303
|
+
const execute = dependencies.runGhFallback ?? runGhPassthroughCommand;
|
|
304
|
+
return execute(argv);
|
|
305
|
+
}
|
|
306
|
+
function runGhPassthroughCommand(argv) {
|
|
307
|
+
const result = spawnSync("gh", [...argv], { stdio: "inherit" });
|
|
308
|
+
if (result.error)
|
|
309
|
+
throw new CliError("GH_FALLBACK_FAILED", `Cannot execute gh: ${result.error.message}.`);
|
|
310
|
+
return result.status ?? EXIT_INTERNAL;
|
|
311
|
+
}
|
|
283
312
|
function hasInariExtension(output) {
|
|
284
313
|
return output.split(/\r?\n/u).some((line) => /^\s*gh\s+inari(?:\s|$)/u.test(line));
|
|
285
314
|
}
|
|
@@ -474,6 +503,12 @@ async function runArtifactCommand(domain, command, rest, parsed, root, dependenc
|
|
|
474
503
|
console.log(JSON.stringify({ ok: true, artifact: created.artifact, governance: created.governance }));
|
|
475
504
|
return 0;
|
|
476
505
|
}
|
|
506
|
+
if (command === "check" || command === "edit" || command === "normalize" || command === "sync") {
|
|
507
|
+
if (rest[0] === undefined || !isPositiveInteger(rest[0])) {
|
|
508
|
+
throw invalidArtifactNumberError(domain, rest[0]);
|
|
509
|
+
}
|
|
510
|
+
return runExistingRemediation(domain, command, Number(rest[0]), parsed, root, dependencies, json);
|
|
511
|
+
}
|
|
477
512
|
if ((command === "validate" || command === "explain") &&
|
|
478
513
|
rest[0] !== undefined &&
|
|
479
514
|
isPositiveInteger(rest[0]) &&
|
|
@@ -493,7 +528,9 @@ async function runArtifactCommand(domain, command, rest, parsed, root, dependenc
|
|
|
493
528
|
}
|
|
494
529
|
async function runExistingValidation(domain, number, parsed, root, dependencies, json) {
|
|
495
530
|
rejectGovernedPolicyOverride(parsed.options.policy);
|
|
496
|
-
const
|
|
531
|
+
const adapter = createAdapter(dependencies, root, parsed.options.repository);
|
|
532
|
+
await adapter.resolveRepositoryContext();
|
|
533
|
+
const read = await readGovernedExistingArtifact(adapter, domain, number, templateSelector(parsed, undefined));
|
|
497
534
|
const { remote, result } = read;
|
|
498
535
|
const projection = projectExistingArtifact(result);
|
|
499
536
|
const output = {
|
|
@@ -509,7 +546,9 @@ async function runExistingValidation(domain, number, parsed, root, dependencies,
|
|
|
509
546
|
}
|
|
510
547
|
async function runExistingGet(domain, number, parsed, root, dependencies) {
|
|
511
548
|
rejectGovernedPolicyOverride(parsed.options.policy);
|
|
512
|
-
const
|
|
549
|
+
const adapter = createAdapter(dependencies, root, parsed.options.repository);
|
|
550
|
+
await adapter.resolveRepositoryContext();
|
|
551
|
+
const { remote, contract, result } = await readGovernedExistingArtifact(adapter, domain, number, templateSelector(parsed, undefined));
|
|
513
552
|
const projection = projectExistingArtifact(result);
|
|
514
553
|
const output = {
|
|
515
554
|
valid: projection.valid,
|
|
@@ -527,6 +566,74 @@ async function runExistingGet(domain, number, parsed, root, dependencies) {
|
|
|
527
566
|
console.log(JSON.stringify(output));
|
|
528
567
|
return result.valid ? 0 : EXIT_VALIDATION;
|
|
529
568
|
}
|
|
569
|
+
async function runExistingRemediation(domain, operation, number, parsed, root, dependencies, json) {
|
|
570
|
+
void json;
|
|
571
|
+
rejectGovernedPolicyOverride(parsed.options.policy);
|
|
572
|
+
const adapter = createAdapter(dependencies, root, parsed.options.repository);
|
|
573
|
+
await adapter.resolveRepositoryContext();
|
|
574
|
+
const read = await readGovernedExistingArtifact(adapter, domain, number, templateSelector(parsed, undefined));
|
|
575
|
+
const assessment = assessExistingArtifact(domain, read);
|
|
576
|
+
const base = {
|
|
577
|
+
operation,
|
|
578
|
+
kind: domain === "issue" ? "issue" : "pull_request",
|
|
579
|
+
number: read.remote.number,
|
|
580
|
+
url: read.remote.url,
|
|
581
|
+
...(read.contract === undefined ? {} : { template: read.contract.templateIdentity }),
|
|
582
|
+
};
|
|
583
|
+
if (operation === "check") {
|
|
584
|
+
console.log(JSON.stringify({
|
|
585
|
+
ok: assessment.status === "valid-current",
|
|
586
|
+
...base,
|
|
587
|
+
status: assessment.status,
|
|
588
|
+
classification: read.result.classification,
|
|
589
|
+
valid: assessment.status === "valid-current",
|
|
590
|
+
normalizable: assessment.normalizable,
|
|
591
|
+
diagnostics: assessment.diagnostics,
|
|
592
|
+
violations: assessment.status === "non-canonical" ? assessment.diagnostics : read.result.violations,
|
|
593
|
+
}));
|
|
594
|
+
return assessment.status === "valid-current" ? 0 : EXIT_VALIDATION;
|
|
595
|
+
}
|
|
596
|
+
if (read.contract === undefined) {
|
|
597
|
+
throw new RemediationError(operation === "normalize" ? "NORMALIZATION_UNSAFE" : "SYNC_CURRENT_UNSUPPORTED", "No authoritative template could be selected for the existing artifact.", "$.template");
|
|
598
|
+
}
|
|
599
|
+
let desiredInput;
|
|
600
|
+
if (operation === "normalize") {
|
|
601
|
+
if (!read.result.valid || !read.result.parse.parsed) {
|
|
602
|
+
throw new RemediationError("NORMALIZATION_UNSAFE", "Normalization requires a semantically valid artifact whose values can be round-tripped canonically.", "$.artifact");
|
|
603
|
+
}
|
|
604
|
+
desiredInput = currentArtifactInput(domain, read);
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
const input = await readInputDocument(parsed.options.from);
|
|
608
|
+
desiredInput =
|
|
609
|
+
operation === "edit" ? applySemanticPatch(domain, read, input) : prepareSyncInput(domain, read, input);
|
|
610
|
+
}
|
|
611
|
+
const desired = prepareRemediationArtifact(domain, read.contract, desiredInput);
|
|
612
|
+
const diff = diffArtifact(domain, read, desired);
|
|
613
|
+
const resultBase = {
|
|
614
|
+
...base,
|
|
615
|
+
changed: diff.changed,
|
|
616
|
+
noOp: !diff.changed,
|
|
617
|
+
diff,
|
|
618
|
+
};
|
|
619
|
+
if (!diff.changed || parsed.options.dryRun === true) {
|
|
620
|
+
console.log(JSON.stringify({
|
|
621
|
+
ok: true,
|
|
622
|
+
...resultBase,
|
|
623
|
+
...(parsed.options.dryRun === true ? { dryRun: true, mutation: "not-performed" } : {}),
|
|
624
|
+
}));
|
|
625
|
+
return 0;
|
|
626
|
+
}
|
|
627
|
+
const mutated = await updateGovernedExistingArtifact(adapter, domain, number, desired);
|
|
628
|
+
console.log(JSON.stringify({
|
|
629
|
+
ok: true,
|
|
630
|
+
...resultBase,
|
|
631
|
+
mutation: "applied",
|
|
632
|
+
artifact: { number: mutated.artifact.number, url: mutated.artifact.url },
|
|
633
|
+
governance: mutated.governance,
|
|
634
|
+
}));
|
|
635
|
+
return 0;
|
|
636
|
+
}
|
|
530
637
|
function existingArtifactMetadata(domain, remote) {
|
|
531
638
|
if (domain === "issue") {
|
|
532
639
|
if (!("labels" in remote) || !("assignees" in remote))
|
|
@@ -548,58 +655,6 @@ function existingArtifactMetadata(domain, remote) {
|
|
|
548
655
|
base: remote.base,
|
|
549
656
|
};
|
|
550
657
|
}
|
|
551
|
-
async function readExistingArtifact(domain, number, parsed, root, dependencies) {
|
|
552
|
-
const adapter = createAdapter(dependencies, root, parsed.options.repository);
|
|
553
|
-
await adapter.resolveRepositoryContext();
|
|
554
|
-
const selector = templateSelector(parsed, undefined);
|
|
555
|
-
let contracts;
|
|
556
|
-
let failedTemplates;
|
|
557
|
-
if (selector === undefined) {
|
|
558
|
-
const outcomes = await compileRepositoryGovernedContracts(adapter, domain);
|
|
559
|
-
contracts = outcomes.filter((outcome) => outcome.status === "compiled").map((outcome) => outcome.contract);
|
|
560
|
-
failedTemplates = outcomes.filter((outcome) => outcome.status === "failed");
|
|
561
|
-
}
|
|
562
|
-
else {
|
|
563
|
-
contracts = [await compileRepositoryGovernedContract(adapter, domain, selector)];
|
|
564
|
-
failedTemplates = [];
|
|
565
|
-
}
|
|
566
|
-
const remote = domain === "issue" ? await adapter.getIssue(number) : await adapter.getPullRequest(number);
|
|
567
|
-
const candidates = contracts.map((contract) => ({
|
|
568
|
-
contract,
|
|
569
|
-
result: domain === "issue"
|
|
570
|
-
? validateExistingIssueArtifact(contract, remote.body)
|
|
571
|
-
: validateExistingPullRequestArtifact(contract, remote.body),
|
|
572
|
-
}));
|
|
573
|
-
const selected = selectExistingArtifactCandidate(candidates);
|
|
574
|
-
if (selected.contract !== undefined || failedTemplates.length === 0) {
|
|
575
|
-
return { remote, contract: selected.contract, result: selected.result };
|
|
576
|
-
}
|
|
577
|
-
// No compiled template matched, and at least one sibling template failed to
|
|
578
|
-
// compile: fail closed, since the malformed template could be the one that
|
|
579
|
-
// actually owns this artifact. Surface it as a bounded diagnostic rather
|
|
580
|
-
// than an opaque compile error.
|
|
581
|
-
const compileDiagnostics = failedTemplates.map((failed) => ({
|
|
582
|
-
code: "EXISTING_TEMPLATE_COMPILE_FAILED",
|
|
583
|
-
path: failed.path,
|
|
584
|
-
message: `[${failed.path}] Template failed to compile: ${failed.message}`,
|
|
585
|
-
}));
|
|
586
|
-
// selected.contract is undefined here, so selectExistingArtifactCandidate resolved this
|
|
587
|
-
// as an unmatched candidate set: its violations are always ExistingArtifactDiagnostic[].
|
|
588
|
-
const existingViolations = selected.result.violations;
|
|
589
|
-
return {
|
|
590
|
-
remote,
|
|
591
|
-
result: {
|
|
592
|
-
valid: false,
|
|
593
|
-
classification: selected.result.classification,
|
|
594
|
-
parse: {
|
|
595
|
-
parsed: false,
|
|
596
|
-
values: {},
|
|
597
|
-
diagnostics: [...selected.result.parse.diagnostics, ...compileDiagnostics],
|
|
598
|
-
},
|
|
599
|
-
violations: [...existingViolations, ...compileDiagnostics],
|
|
600
|
-
},
|
|
601
|
-
};
|
|
602
|
-
}
|
|
603
658
|
function createAdapter(dependencies, root, repository) {
|
|
604
659
|
const factory = dependencies.createAdapter ?? ((options) => new GitHubAdapter(options));
|
|
605
660
|
return factory({ cwd: root, ...(typeof repository === "string" ? { repository } : {}) });
|
|
@@ -677,10 +732,14 @@ function parseArguments(argv) {
|
|
|
677
732
|
options[key] = true;
|
|
678
733
|
continue;
|
|
679
734
|
}
|
|
680
|
-
const
|
|
681
|
-
if (
|
|
735
|
+
const rawValue = token.slice(equalIndex + 1);
|
|
736
|
+
if (key === "help" && rawValue === "full") {
|
|
737
|
+
options[key] = rawValue;
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
if (rawValue !== "true" && rawValue !== "false")
|
|
682
741
|
throw new CliError("INVALID_OPTION", `Option --${rawKey} must be true or false.`);
|
|
683
|
-
options[key] =
|
|
742
|
+
options[key] = rawValue === "true";
|
|
684
743
|
continue;
|
|
685
744
|
}
|
|
686
745
|
if (!VALUE_OPTIONS.has(key))
|
|
@@ -717,7 +776,9 @@ function toErrorShape(error) {
|
|
|
717
776
|
return { code: "INTERNAL_ERROR", message: error instanceof Error ? error.message : "Operation failed." };
|
|
718
777
|
}
|
|
719
778
|
function classifyExitCode(error) {
|
|
720
|
-
if (error instanceof SemanticValidationError ||
|
|
779
|
+
if (error instanceof SemanticValidationError ||
|
|
780
|
+
error instanceof ArtifactInputError ||
|
|
781
|
+
error instanceof RemediationError)
|
|
721
782
|
return EXIT_VALIDATION;
|
|
722
783
|
if (isGitHubAdapterError(error))
|
|
723
784
|
return EXIT_REMOTE;
|
|
@@ -744,14 +805,36 @@ function classifyExitCode(error) {
|
|
|
744
805
|
return EXIT_VALIDATION;
|
|
745
806
|
return EXIT_INTERNAL;
|
|
746
807
|
}
|
|
808
|
+
/**
|
|
809
|
+
* True when argv targets a command gh-inari implements; false means it must fall back to the real `gh` binary.
|
|
810
|
+
* `--help` on an unowned domain or subcommand (e.g. `repo view --help`, `pr list --help`) is not claimed here so
|
|
811
|
+
* that help delegates to real `gh` the same way execution does -- Inari does not reproduce upstream help text.
|
|
812
|
+
*/
|
|
813
|
+
function isOwnedInvocation(argv) {
|
|
814
|
+
const first = argv.find((token) => !token.startsWith("--"));
|
|
815
|
+
if (first === undefined)
|
|
816
|
+
return true;
|
|
817
|
+
if (first === "diagnose" || first === "doctor" || first === "version" || first === "help")
|
|
818
|
+
return true;
|
|
819
|
+
if (argv.includes("--version") || argv.includes("--diagnose") || argv.includes("--doctor"))
|
|
820
|
+
return true;
|
|
821
|
+
const helpRequested = argv.some((token) => token === "--help" || token.startsWith("--help="));
|
|
822
|
+
if (first === "template") {
|
|
823
|
+
const second = argv.slice(argv.indexOf(first) + 1).find((token) => !token.startsWith("--"));
|
|
824
|
+
if (helpRequested && second === undefined)
|
|
825
|
+
return true;
|
|
826
|
+
return second !== undefined && KNOWN_TEMPLATE_COMMANDS.has(second);
|
|
827
|
+
}
|
|
828
|
+
if (first === "issue" || first === "pr") {
|
|
829
|
+
const second = argv.slice(argv.indexOf(first) + 1).find((token) => !token.startsWith("--"));
|
|
830
|
+
if (helpRequested && second === undefined)
|
|
831
|
+
return true;
|
|
832
|
+
return second !== undefined && KNOWN_ARTIFACT_COMMANDS.has(second);
|
|
833
|
+
}
|
|
834
|
+
return false;
|
|
835
|
+
}
|
|
747
836
|
function isMachineCommand(positionals) {
|
|
748
|
-
return ((positionals.length >= 2 &&
|
|
749
|
-
(positionals[1] === "schema" ||
|
|
750
|
-
positionals[1] === "validate" ||
|
|
751
|
-
positionals[1] === "render" ||
|
|
752
|
-
positionals[1] === "create" ||
|
|
753
|
-
positionals[1] === "explain" ||
|
|
754
|
-
positionals[1] === "get")) ||
|
|
837
|
+
return ((positionals.length >= 2 && KNOWN_ARTIFACT_COMMANDS.has(positionals[1] ?? "")) ||
|
|
755
838
|
positionals[0] === "diagnose" ||
|
|
756
839
|
positionals[0] === "doctor" ||
|
|
757
840
|
positionals[0] === "version");
|
|
@@ -765,12 +848,7 @@ function isMachineCommandTokens(argv) {
|
|
|
765
848
|
if (domainIndex < 0)
|
|
766
849
|
return false;
|
|
767
850
|
const command = argv[domainIndex + 1];
|
|
768
|
-
return
|
|
769
|
-
command === "validate" ||
|
|
770
|
-
command === "render" ||
|
|
771
|
-
command === "create" ||
|
|
772
|
-
command === "explain" ||
|
|
773
|
-
command === "get");
|
|
851
|
+
return command !== undefined && KNOWN_ARTIFACT_COMMANDS.has(command);
|
|
774
852
|
}
|
|
775
853
|
function isPositiveInteger(value) {
|
|
776
854
|
return /^[1-9]\d*$/u.test(value);
|
|
@@ -805,8 +883,166 @@ async function readStdin() {
|
|
|
805
883
|
}
|
|
806
884
|
return Buffer.concat(chunks).toString("utf8");
|
|
807
885
|
}
|
|
808
|
-
|
|
809
|
-
|
|
886
|
+
const TEMPLATE_LEAVES = {
|
|
887
|
+
list: {
|
|
888
|
+
usage: "template list",
|
|
889
|
+
summary: "List discovered repository-native and semantic templates.",
|
|
890
|
+
example: "inari template list",
|
|
891
|
+
},
|
|
892
|
+
sync: {
|
|
893
|
+
usage: "template sync [--check]",
|
|
894
|
+
summary: "Regenerate native GitHub templates from semantic template contracts under .github/inari/.",
|
|
895
|
+
example: "inari template sync --check",
|
|
896
|
+
},
|
|
897
|
+
import: {
|
|
898
|
+
usage: "template import --from <native-template> [--to <semantic-file>]",
|
|
899
|
+
summary: "Import a native GitHub template into a semantic template contract. Discovered semantic paths: " +
|
|
900
|
+
".github/inari/issues/<id>.json, .github/inari/pull-request.json (single PR template), or " +
|
|
901
|
+
".github/inari/pull-requests/<id>.json (multiple PR templates). Other --to paths write successfully " +
|
|
902
|
+
"but are never discovered.",
|
|
903
|
+
example: "inari template import --from .github/ISSUE_TEMPLATE/feature.yml",
|
|
904
|
+
},
|
|
905
|
+
};
|
|
906
|
+
function artifactLeaves(domain) {
|
|
907
|
+
const noun = domain === "issue" ? "issue" : "pull request";
|
|
908
|
+
return {
|
|
909
|
+
schema: {
|
|
910
|
+
usage: `${domain} schema [template]`,
|
|
911
|
+
summary: `Print the semantic field schema for a ${noun} template.`,
|
|
912
|
+
example: `inari ${domain} schema feature --compact`,
|
|
913
|
+
},
|
|
914
|
+
validate: {
|
|
915
|
+
usage: `${domain} validate --template <template> --from <file.json>`,
|
|
916
|
+
summary: `Validate local JSON input against a template's schema. ` +
|
|
917
|
+
`To validate an existing ${noun} instead, use \`${domain} validate <number> [--template <template>]\`.`,
|
|
918
|
+
example: `inari ${domain} validate --template feature --from ${domain}.json`,
|
|
919
|
+
},
|
|
920
|
+
render: {
|
|
921
|
+
usage: `${domain} render --template <template> --from <file.json>`,
|
|
922
|
+
summary: `Render validated JSON input into canonical Markdown without mutating GitHub.`,
|
|
923
|
+
example: `inari ${domain} render --template feature --from ${domain}.json`,
|
|
924
|
+
},
|
|
925
|
+
create: {
|
|
926
|
+
usage: `${domain} create --template <template> --from <file.json>`,
|
|
927
|
+
summary: `Validate, render, and create a governed ${noun} on GitHub.`,
|
|
928
|
+
example: `inari ${domain} create --template feature --from ${domain}.json`,
|
|
929
|
+
},
|
|
930
|
+
explain: {
|
|
931
|
+
usage: `${domain} explain <number> [--template <template>]`,
|
|
932
|
+
summary: `Explain why an existing ${noun} does or does not satisfy its governed contract.`,
|
|
933
|
+
example: `inari ${domain} explain 123`,
|
|
934
|
+
},
|
|
935
|
+
get: {
|
|
936
|
+
usage: `${domain} get <number> [--template <template>] --json`,
|
|
937
|
+
summary: `Project an existing ${noun} as its canonical semantic JSON.`,
|
|
938
|
+
example: `inari ${domain} get 123 --json`,
|
|
939
|
+
},
|
|
940
|
+
check: {
|
|
941
|
+
usage: `${domain} check <number> [--template <template>]`,
|
|
942
|
+
summary: `Check whether an existing ${noun} is normalizable without mutating GitHub.`,
|
|
943
|
+
example: `inari ${domain} check 123`,
|
|
944
|
+
},
|
|
945
|
+
edit: {
|
|
946
|
+
usage: `${domain} edit <number> --from <file.json> [--dry-run]`,
|
|
947
|
+
summary: `Apply an explicit semantic patch to an existing ${noun}.`,
|
|
948
|
+
example: `inari ${domain} edit 123 --from patch.json --dry-run`,
|
|
949
|
+
},
|
|
950
|
+
normalize: {
|
|
951
|
+
usage: `${domain} normalize <number> [--dry-run]`,
|
|
952
|
+
summary: `Repair an existing ${noun}'s native projection while preserving its existing semantic values.`,
|
|
953
|
+
example: `inari ${domain} normalize 123 --dry-run`,
|
|
954
|
+
},
|
|
955
|
+
sync: {
|
|
956
|
+
usage: `${domain} sync <number> --from <file.json> [--dry-run]`,
|
|
957
|
+
summary: `Reconcile an existing ${noun} to a complete desired semantic state.`,
|
|
958
|
+
example: `inari ${domain} sync 123 --from desired.json --dry-run`,
|
|
959
|
+
},
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
const GLOBAL_OPTIONS = ` --from <path> JSON input file, or - for stdin
|
|
963
|
+
--template <id> Repository-native template id, path, or unique name
|
|
964
|
+
--policy <path> Local PR policy for schema/validate/render --from workflows; forbidden for governed remote operations
|
|
965
|
+
--repository <r> GitHub repository override; governed commands use its default-branch governance
|
|
966
|
+
--title <title> Issue/PR title for create
|
|
967
|
+
--head <branch> PR head branch for create
|
|
968
|
+
--base <branch> PR base branch for create
|
|
969
|
+
--compact Emit only semantic fields and constraints for schema
|
|
970
|
+
--check Check generated native projections without writing
|
|
971
|
+
--dry-run Show a bounded remediation diff without mutating GitHub
|
|
972
|
+
--draft Create the PR as a draft
|
|
973
|
+
--maintainer-can-modify
|
|
974
|
+
Allow maintainer edits on the PR
|
|
975
|
+
--json Emit structured JSON output
|
|
976
|
+
--version Print package version
|
|
977
|
+
--diagnose Check the canonical gh extension and recovery path
|
|
978
|
+
--require-capability <id>
|
|
979
|
+
Require a capability in --version/--diagnose checks
|
|
980
|
+
--minimum-version <v>
|
|
981
|
+
Require a minimum semantic version in checks
|
|
982
|
+
--help Print this help (--help=full for the complete reference)`;
|
|
983
|
+
const DOMAIN_PASSTHROUGH_EXAMPLE = {
|
|
984
|
+
issue: "issue list",
|
|
985
|
+
pr: "pr checks",
|
|
986
|
+
template: "template view",
|
|
987
|
+
};
|
|
988
|
+
/** Dispatches to root, domain, or leaf help by command depth; positionals are pre-parse-error tokens, so any --help value routes here. */
|
|
989
|
+
function printHelpFor(positionals, helpValue) {
|
|
990
|
+
if (helpValue === "full")
|
|
991
|
+
return printFullHelp();
|
|
992
|
+
const [domain, command] = positionals;
|
|
993
|
+
if (domain === "issue" || domain === "pr") {
|
|
994
|
+
if (command !== undefined && command in artifactLeaves(domain))
|
|
995
|
+
return printLeafHelp(artifactLeaves(domain)[command]);
|
|
996
|
+
return printDomainHelp(domain, artifactLeaves(domain));
|
|
997
|
+
}
|
|
998
|
+
if (domain === "template") {
|
|
999
|
+
if (command !== undefined && command in TEMPLATE_LEAVES)
|
|
1000
|
+
return printLeafHelp(TEMPLATE_LEAVES[command]);
|
|
1001
|
+
return printDomainHelp("template", TEMPLATE_LEAVES);
|
|
1002
|
+
}
|
|
1003
|
+
printRootHelp();
|
|
1004
|
+
}
|
|
1005
|
+
function printRootHelp() {
|
|
1006
|
+
console.log(`Usage: inari <command> [...]
|
|
1007
|
+
|
|
1008
|
+
A governed GitHub CLI. Issue and PR commands under governed templates run
|
|
1009
|
+
through Inari; every other command passes through to the real gh binary
|
|
1010
|
+
with the original argv and exit status.
|
|
1011
|
+
|
|
1012
|
+
Domains:
|
|
1013
|
+
issue Governed Issue schema, validation, rendering, and lifecycle
|
|
1014
|
+
pr Governed pull request schema, validation, rendering, and lifecycle
|
|
1015
|
+
template Semantic template authoring and native template sync
|
|
1016
|
+
|
|
1017
|
+
All other commands (e.g. repo, auth, pr list, issue view) are passed through to gh.
|
|
1018
|
+
|
|
1019
|
+
Run \`inari <domain> --help\` for that domain's operations.
|
|
1020
|
+
Run \`inari --help=full\` for the complete command and option reference.
|
|
1021
|
+
Run \`inari --version\` or \`inari --diagnose\` for machine-readable runtime checks.`);
|
|
1022
|
+
}
|
|
1023
|
+
function printDomainHelp(domain, leaves) {
|
|
1024
|
+
const lines = Object.values(leaves).map((leaf) => ` ${leaf.usage}`);
|
|
1025
|
+
console.log(`Usage: inari ${domain} <command> [...]
|
|
1026
|
+
|
|
1027
|
+
Operations:
|
|
1028
|
+
${lines.join("\n")}
|
|
1029
|
+
|
|
1030
|
+
Commands outside this list under "${domain}" (e.g. \`${DOMAIN_PASSTHROUGH_EXAMPLE[domain]}\`) pass through to gh.
|
|
1031
|
+
|
|
1032
|
+
Run \`inari ${domain} <command> --help\` for that command's inputs and an example.`);
|
|
1033
|
+
}
|
|
1034
|
+
function printLeafHelp(leaf) {
|
|
1035
|
+
console.log(`Usage: inari ${leaf.usage}
|
|
1036
|
+
|
|
1037
|
+
${leaf.summary}
|
|
1038
|
+
|
|
1039
|
+
Example:
|
|
1040
|
+
${leaf.example}
|
|
1041
|
+
|
|
1042
|
+
Run \`inari --help=full\` for the complete option reference.`);
|
|
1043
|
+
}
|
|
1044
|
+
function printFullHelp() {
|
|
1045
|
+
console.log(`Usage: inari <command> [options]
|
|
810
1046
|
|
|
811
1047
|
Commands:
|
|
812
1048
|
template list
|
|
@@ -823,6 +1059,10 @@ Commands:
|
|
|
823
1059
|
issue validate <number> [--template <template>]
|
|
824
1060
|
issue explain <number> [--template <template>]
|
|
825
1061
|
issue get <number> [--template <template>] --json
|
|
1062
|
+
issue check <number> [--template <template>]
|
|
1063
|
+
issue edit <number> --from <file.json> [--dry-run]
|
|
1064
|
+
issue normalize <number> [--dry-run]
|
|
1065
|
+
issue sync <number> --from <file.json> [--dry-run]
|
|
826
1066
|
pr schema [template]
|
|
827
1067
|
pr validate --template <template> --from <file.json>
|
|
828
1068
|
pr render --template <template> --from <file.json>
|
|
@@ -830,32 +1070,21 @@ Commands:
|
|
|
830
1070
|
pr validate <number> [--template <template>]
|
|
831
1071
|
pr explain <number> [--template <template>]
|
|
832
1072
|
pr get <number> [--template <template>] --json
|
|
1073
|
+
pr check <number> [--template <template>]
|
|
1074
|
+
pr edit <number> --from <file.json> [--dry-run]
|
|
1075
|
+
pr normalize <number> [--dry-run]
|
|
1076
|
+
pr sync <number> --from <file.json> [--dry-run]
|
|
833
1077
|
|
|
834
1078
|
Options:
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
--title <title> Issue/PR title for create
|
|
840
|
-
--head <branch> PR head branch for create
|
|
841
|
-
--base <branch> PR base branch for create
|
|
842
|
-
--compact Emit only semantic fields and constraints for schema
|
|
843
|
-
--check Check generated native projections without writing
|
|
844
|
-
--draft Create the PR as a draft
|
|
845
|
-
--maintainer-can-modify
|
|
846
|
-
Allow maintainer edits on the PR
|
|
847
|
-
--json Emit structured JSON output
|
|
848
|
-
--version Print package version
|
|
849
|
-
--diagnose Check the canonical gh extension and recovery path
|
|
850
|
-
--require-capability <id>
|
|
851
|
-
Require a capability in --version/--diagnose checks
|
|
852
|
-
--minimum-version <v>
|
|
853
|
-
Require a minimum semantic version in checks
|
|
854
|
-
--help Print this help
|
|
1079
|
+
${GLOBAL_OPTIONS}
|
|
1080
|
+
|
|
1081
|
+
Create always validates and renders before invoking gh. Schema, validate, render, check, and --dry-run remediation never mutate GitHub.
|
|
1082
|
+
Edit applies an explicit semantic patch; normalize preserves existing semantic values; sync reconciles a complete desired semantic state.
|
|
855
1083
|
|
|
856
|
-
|
|
1084
|
+
All other commands pass through to the real gh binary unchanged.
|
|
857
1085
|
|
|
858
|
-
Canonical
|
|
859
|
-
PATH-independent fallback: npx --yes gh-inari
|
|
1086
|
+
Canonical install: npm install --global gh-inari
|
|
1087
|
+
PATH-independent fallback: npx --yes gh-inari
|
|
1088
|
+
Extension compatibility path: gh extension install yohn-jp/gh-inari`);
|
|
860
1089
|
}
|
|
861
1090
|
//# sourceMappingURL=cli.js.map
|