@topogram/cli 0.3.62 → 0.3.64
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/package.json +1 -1
- package/src/adoption/plan.d.ts +6 -0
- package/src/adoption/reporting.d.ts +10 -0
- package/src/adoption/review-groups.d.ts +6 -0
- package/src/agent-brief.d.ts +3 -0
- package/src/agent-brief.js +495 -0
- package/src/agent-ops/query-builders.d.ts +26 -0
- package/src/archive/archive.d.ts +2 -0
- package/src/archive/compact.d.ts +1 -0
- package/src/archive/unarchive.d.ts +1 -0
- package/src/catalog.d.ts +10 -0
- package/src/catalog.js +62 -66
- package/src/cli/catalog-alias.d.ts +1 -0
- package/src/cli/command-parser.js +38 -0
- package/src/cli/command-parsers/core.js +102 -0
- package/src/cli/command-parsers/generator.js +39 -0
- package/src/cli/command-parsers/import.js +44 -0
- package/src/cli/command-parsers/legacy-workflow.js +21 -0
- package/src/cli/command-parsers/project.js +47 -0
- package/src/cli/command-parsers/sdlc.js +47 -0
- package/src/cli/command-parsers/shared.js +51 -0
- package/src/cli/command-parsers/template.js +48 -0
- package/src/cli/commands/agent.js +47 -0
- package/src/cli/commands/catalog.js +617 -0
- package/src/cli/commands/check.js +268 -0
- package/src/cli/commands/doctor.js +268 -0
- package/src/cli/commands/emit.js +149 -0
- package/src/cli/commands/generate.js +96 -0
- package/src/cli/commands/generator-policy.js +785 -0
- package/src/cli/commands/generator.js +443 -0
- package/src/cli/commands/import-runner.js +157 -0
- package/src/cli/commands/import.js +1734 -0
- package/src/cli/commands/inspect.js +55 -0
- package/src/cli/commands/new.js +94 -0
- package/src/cli/commands/package.js +815 -0
- package/src/cli/commands/query.js +1302 -0
- package/src/cli/commands/release-rollout.js +257 -0
- package/src/cli/commands/release-shared.js +528 -0
- package/src/cli/commands/release-status.js +429 -0
- package/src/cli/commands/release.js +107 -0
- package/src/cli/commands/sdlc.js +168 -0
- package/src/cli/commands/setup.js +76 -0
- package/src/cli/commands/source.js +291 -0
- package/src/cli/commands/template-runner.js +198 -0
- package/src/cli/commands/template.js +2145 -0
- package/src/cli/commands/trust.js +219 -0
- package/src/cli/commands/version.js +40 -0
- package/src/cli/commands/widget.js +168 -0
- package/src/cli/commands/workflow.js +63 -0
- package/src/cli/dispatcher.js +392 -0
- package/src/cli/help-dispatch.js +188 -0
- package/src/cli/help.js +296 -0
- package/src/cli/migration-guidance.js +59 -0
- package/src/cli/options.js +96 -0
- package/src/cli/output-safety.js +107 -0
- package/src/cli/path-normalization.js +29 -0
- package/src/cli.js +47 -11711
- package/src/example-implementation.d.ts +2 -0
- package/src/format.d.ts +1 -0
- package/src/generator/check.d.ts +1 -0
- package/src/generator/context/bundle.d.ts +1 -0
- package/src/generator/context/shared.d.ts +2 -0
- package/src/generator/native/parity-bundle.js +2 -1
- package/src/generator/surfaces/web/html-escape.js +22 -0
- package/src/generator/surfaces/web/react.js +10 -8
- package/src/generator/surfaces/web/sveltekit.js +7 -5
- package/src/generator/surfaces/web/vanilla.js +8 -4
- package/src/generator.d.ts +2 -0
- package/src/github-client.js +520 -0
- package/src/import/core/shared.js +20 -62
- package/src/import/extractors/api/flutter-dio.js +4 -8
- package/src/import/extractors/api/react-native-repository.js +4 -8
- package/src/import/index.d.ts +4 -0
- package/src/import/provenance.d.ts +4 -0
- package/src/new-project.js +100 -11
- package/src/npm-safety.js +79 -0
- package/src/parser.d.ts +1 -0
- package/src/path-helpers.d.ts +1 -0
- package/src/path-helpers.js +20 -0
- package/src/project-config.js +1 -0
- package/src/reconcile/docs.d.ts +8 -0
- package/src/reconcile/journeys.d.ts +1 -0
- package/src/resolver.d.ts +1 -0
- package/src/runtime-support.js +29 -0
- package/src/sdlc/adopt.d.ts +1 -0
- package/src/sdlc/check.d.ts +1 -0
- package/src/sdlc/explain.d.ts +1 -0
- package/src/sdlc/release.d.ts +1 -0
- package/src/sdlc/scaffold.d.ts +1 -0
- package/src/sdlc/transition.d.ts +1 -0
- package/src/text-helpers.d.ts +6 -0
- package/src/text-helpers.js +245 -0
- package/src/topogram-config.js +306 -0
- package/src/validator.d.ts +2 -0
- package/src/workflows/adoption/index.js +26 -0
- package/src/workflows/docs-generate.js +262 -0
- package/src/workflows/docs-scan.js +703 -0
- package/src/workflows/docs.js +15 -0
- package/src/workflows/import-app/api.js +799 -0
- package/src/workflows/import-app/db.js +538 -0
- package/src/workflows/import-app/index.js +30 -0
- package/src/workflows/import-app/shared.js +218 -0
- package/src/workflows/import-app/ui.js +443 -0
- package/src/workflows/import-app/workflow.js +159 -0
- package/src/workflows/reconcile/adoption-plan.js +742 -0
- package/src/workflows/reconcile/auth.js +692 -0
- package/src/workflows/reconcile/bundle-core.js +600 -0
- package/src/workflows/reconcile/bundle-shared.js +75 -0
- package/src/workflows/reconcile/candidate-model.js +477 -0
- package/src/workflows/reconcile/canonical-surface.js +264 -0
- package/src/workflows/reconcile/gap-report.js +333 -0
- package/src/workflows/reconcile/ids.js +6 -0
- package/src/workflows/reconcile/impacts.js +625 -0
- package/src/workflows/reconcile/index.js +7 -0
- package/src/workflows/reconcile/renderers.js +461 -0
- package/src/workflows/reconcile/summary.js +90 -0
- package/src/workflows/reconcile/workflow.js +309 -0
- package/src/workflows/shared.js +189 -0
- package/src/workflows/types.d.ts +93 -0
- package/src/workflows.d.ts +1 -0
- package/src/workflows.js +10 -7652
package/src/catalog.js
CHANGED
|
@@ -6,11 +6,52 @@ import fs from "node:fs";
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
|
|
8
8
|
import { installPackageSpec } from "./new-project.js";
|
|
9
|
+
import { DEFAULT_CATALOG_SOURCE, defaultCatalogSource } from "./topogram-config.js";
|
|
10
|
+
import { readGithubCatalogSourceText } from "./github-client.js";
|
|
9
11
|
|
|
10
|
-
export const DEFAULT_CATALOG_SOURCE = "https://raw.githubusercontent.com/attebury/topograms/main/topograms.catalog.json";
|
|
11
12
|
export const CATALOG_FILE_NAME = "topograms.catalog.json";
|
|
12
13
|
export const TOPOGRAM_SOURCE_FILE = ".topogram-source.json";
|
|
13
14
|
const KNOWN_CATALOG_SURFACES = new Set(["web", "api", "database", "native"]);
|
|
15
|
+
const GITHUB_TOKEN_HOSTS = new Set([
|
|
16
|
+
"github.com",
|
|
17
|
+
"api.github.com",
|
|
18
|
+
"raw.githubusercontent.com"
|
|
19
|
+
]);
|
|
20
|
+
const FETCH_URL_SCRIPT = `
|
|
21
|
+
const source = process.argv[1];
|
|
22
|
+
const token = process.env.TOPOGRAM_FETCH_TOKEN || "";
|
|
23
|
+
const tokenHosts = new Set(["github.com", "api.github.com", "raw.githubusercontent.com"]);
|
|
24
|
+
function tokenAllowed(url) {
|
|
25
|
+
const hostname = new URL(url).hostname.toLowerCase();
|
|
26
|
+
return tokenHosts.has(hostname) || hostname.endsWith(".github.com");
|
|
27
|
+
}
|
|
28
|
+
async function readUrl(url, redirects = 0) {
|
|
29
|
+
if (redirects > 5) {
|
|
30
|
+
throw new Error("Too many redirects.");
|
|
31
|
+
}
|
|
32
|
+
const headers = {};
|
|
33
|
+
if (token && tokenAllowed(url)) {
|
|
34
|
+
headers.authorization = "Bearer " + token;
|
|
35
|
+
}
|
|
36
|
+
const response = await fetch(url, { headers, redirect: "manual" });
|
|
37
|
+
if (response.status >= 300 && response.status < 400 && response.headers.get("location")) {
|
|
38
|
+
const next = new URL(response.headers.get("location"), url).toString();
|
|
39
|
+
return readUrl(next, redirects + 1);
|
|
40
|
+
}
|
|
41
|
+
const text = await response.text();
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
const preview = text.trim().slice(0, 400);
|
|
44
|
+
throw new Error(String(response.status) + " " + response.statusText + (preview ? "\\n" + preview : ""));
|
|
45
|
+
}
|
|
46
|
+
return text;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
process.stdout.write(await readUrl(source));
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
`;
|
|
14
55
|
|
|
15
56
|
/**
|
|
16
57
|
* @typedef {Object} CatalogTrust
|
|
@@ -82,7 +123,7 @@ function catalogDiagnostic(input) {
|
|
|
82
123
|
* @returns {string}
|
|
83
124
|
*/
|
|
84
125
|
export function catalogSourceOrDefault(source = null) {
|
|
85
|
-
return source || process.env.TOPOGRAM_CATALOG_SOURCE ||
|
|
126
|
+
return source || process.env.TOPOGRAM_CATALOG_SOURCE || defaultCatalogSource();
|
|
86
127
|
}
|
|
87
128
|
|
|
88
129
|
/**
|
|
@@ -397,65 +438,7 @@ function readCatalogText(source) {
|
|
|
397
438
|
* @returns {string}
|
|
398
439
|
*/
|
|
399
440
|
function readGithubCatalogText(source) {
|
|
400
|
-
|
|
401
|
-
const [pathPart, ref] = spec.split("?ref=");
|
|
402
|
-
const segments = pathPart.split("/").filter(Boolean);
|
|
403
|
-
if (segments.length < 3) {
|
|
404
|
-
throw new Error(`Invalid GitHub catalog source '${source}'. Expected github:owner/repo/path/to/catalog.json.`);
|
|
405
|
-
}
|
|
406
|
-
const [owner, repo, ...fileSegments] = segments;
|
|
407
|
-
const apiPath = `repos/${owner}/${repo}/contents/${fileSegments.join("/")}`;
|
|
408
|
-
const args = ["api", apiPath, "--jq", ".content"];
|
|
409
|
-
if (ref) {
|
|
410
|
-
args.splice(2, 0, "-f", `ref=${ref}`);
|
|
411
|
-
}
|
|
412
|
-
const result = childProcess.spawnSync("gh", args, {
|
|
413
|
-
encoding: "utf8",
|
|
414
|
-
env: {
|
|
415
|
-
...process.env,
|
|
416
|
-
GH_TOKEN: process.env.GH_TOKEN || process.env.GITHUB_TOKEN || process.env.GH_TOKEN || "",
|
|
417
|
-
PATH: process.env.PATH || ""
|
|
418
|
-
}
|
|
419
|
-
});
|
|
420
|
-
if (result.status !== 0) {
|
|
421
|
-
throw new Error(formatGithubCatalogError(source, result));
|
|
422
|
-
}
|
|
423
|
-
return Buffer.from(result.stdout.replace(/\s+/g, ""), "base64").toString("utf8");
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
/**
|
|
427
|
-
* @param {string} source
|
|
428
|
-
* @param {any} result
|
|
429
|
-
* @returns {string}
|
|
430
|
-
*/
|
|
431
|
-
function formatGithubCatalogError(source, result) {
|
|
432
|
-
const output = [result.error?.message, result.stderr, result.stdout].filter(Boolean).join("\n").trim();
|
|
433
|
-
const normalized = output.toLowerCase();
|
|
434
|
-
if (result.error?.code === "ENOENT") {
|
|
435
|
-
return [
|
|
436
|
-
`GitHub CLI (gh) is required to read catalog '${source}'.`,
|
|
437
|
-
"Install gh, or set TOPOGRAM_CATALOG_SOURCE to a local topograms.catalog.json file."
|
|
438
|
-
].join("\n");
|
|
439
|
-
}
|
|
440
|
-
if (/\b(401|403)\b/.test(normalized) || normalized.includes("authentication") || normalized.includes("not logged in") || normalized.includes("forbidden")) {
|
|
441
|
-
return [
|
|
442
|
-
`Authentication is required to read private catalog '${source}'.`,
|
|
443
|
-
"Set GITHUB_TOKEN or GH_TOKEN with repository read access, or run gh auth login.",
|
|
444
|
-
output
|
|
445
|
-
].filter(Boolean).join("\n");
|
|
446
|
-
}
|
|
447
|
-
if (/\b404\b/.test(normalized) || normalized.includes("not found")) {
|
|
448
|
-
return [
|
|
449
|
-
`Catalog source '${source}' was not found, or the current token does not have repository access.`,
|
|
450
|
-
"Check the github:owner/repo/path source and grant repository read access to the token or GitHub Actions workflow.",
|
|
451
|
-
output
|
|
452
|
-
].filter(Boolean).join("\n");
|
|
453
|
-
}
|
|
454
|
-
return [
|
|
455
|
-
`Failed to read catalog '${source}' with gh api.`,
|
|
456
|
-
"Set GITHUB_TOKEN or GH_TOKEN, or run gh auth login.",
|
|
457
|
-
output || "unknown error"
|
|
458
|
-
].join("\n");
|
|
441
|
+
return readGithubCatalogSourceText(source);
|
|
459
442
|
}
|
|
460
443
|
|
|
461
444
|
/**
|
|
@@ -463,15 +446,15 @@ function formatGithubCatalogError(source, result) {
|
|
|
463
446
|
* @returns {string}
|
|
464
447
|
*/
|
|
465
448
|
function readUrlText(source) {
|
|
466
|
-
const args = ["-fsSL", source];
|
|
467
449
|
const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN || "";
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
const result = childProcess.spawnSync("
|
|
450
|
+
const tokenEnv = token && githubTokenAllowedForCatalogUrl(source)
|
|
451
|
+
? { TOPOGRAM_FETCH_TOKEN: token }
|
|
452
|
+
: {};
|
|
453
|
+
const result = childProcess.spawnSync(process.execPath, ["--input-type=module", "-e", FETCH_URL_SCRIPT, source], {
|
|
472
454
|
encoding: "utf8",
|
|
473
455
|
env: {
|
|
474
456
|
...process.env,
|
|
457
|
+
...tokenEnv,
|
|
475
458
|
PATH: process.env.PATH || ""
|
|
476
459
|
}
|
|
477
460
|
});
|
|
@@ -482,6 +465,19 @@ function readUrlText(source) {
|
|
|
482
465
|
return result.stdout;
|
|
483
466
|
}
|
|
484
467
|
|
|
468
|
+
/**
|
|
469
|
+
* @param {string} source
|
|
470
|
+
* @returns {boolean}
|
|
471
|
+
*/
|
|
472
|
+
function githubTokenAllowedForCatalogUrl(source) {
|
|
473
|
+
try {
|
|
474
|
+
const hostname = new URL(source).hostname.toLowerCase();
|
|
475
|
+
return GITHUB_TOKEN_HOSTS.has(hostname) || hostname.endsWith(".github.com");
|
|
476
|
+
} catch {
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
485
481
|
/**
|
|
486
482
|
* @param {TopogramCatalog} catalog
|
|
487
483
|
* @param {string} id
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function resolveCatalogTemplateAlias(templateName: string, catalogSource?: string | null): any;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { parseCoreCommandArgs } from "./command-parsers/core.js";
|
|
4
|
+
import { parseGeneratorCommandArgs } from "./command-parsers/generator.js";
|
|
5
|
+
import { parseImportCommandArgs } from "./command-parsers/import.js";
|
|
6
|
+
import { parseLegacyWorkflowCommandArgs } from "./command-parsers/legacy-workflow.js";
|
|
7
|
+
import { parseProjectCommandArgs } from "./command-parsers/project.js";
|
|
8
|
+
import { parseSdlcCommandArgs } from "./command-parsers/sdlc.js";
|
|
9
|
+
import { parseTemplateCommandArgs } from "./command-parsers/template.js";
|
|
10
|
+
|
|
11
|
+
const COMMAND_PARSERS = [
|
|
12
|
+
parseCoreCommandArgs,
|
|
13
|
+
parseGeneratorCommandArgs,
|
|
14
|
+
parseTemplateCommandArgs,
|
|
15
|
+
parseProjectCommandArgs,
|
|
16
|
+
parseImportCommandArgs,
|
|
17
|
+
parseLegacyWorkflowCommandArgs,
|
|
18
|
+
parseSdlcCommandArgs
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Parses command families that have already been split out of the CLI shim.
|
|
23
|
+
*
|
|
24
|
+
* Keep this module as a composition root. Command-family parsing belongs in
|
|
25
|
+
* `cli/command-parsers/*` so the parser does not become another CLI shim.
|
|
26
|
+
*
|
|
27
|
+
* @param {string[]} args
|
|
28
|
+
* @returns {import("./command-parsers/shared.js").SplitCommandArgs|null}
|
|
29
|
+
*/
|
|
30
|
+
export function parseSplitCommandArgs(args) {
|
|
31
|
+
for (const parser of COMMAND_PARSERS) {
|
|
32
|
+
const commandArgs = parser(args);
|
|
33
|
+
if (commandArgs) {
|
|
34
|
+
return commandArgs;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { commandPath } from "./shared.js";
|
|
4
|
+
|
|
5
|
+
const QUERY_NAMES = new Set([
|
|
6
|
+
"task-mode",
|
|
7
|
+
"diff",
|
|
8
|
+
"slice",
|
|
9
|
+
"adoption-plan",
|
|
10
|
+
"maintained-boundary",
|
|
11
|
+
"maintained-conformance",
|
|
12
|
+
"maintained-drift",
|
|
13
|
+
"seam-check",
|
|
14
|
+
"domain-coverage",
|
|
15
|
+
"domain-list",
|
|
16
|
+
"review-boundary",
|
|
17
|
+
"write-scope",
|
|
18
|
+
"verification-targets",
|
|
19
|
+
"widget-behavior",
|
|
20
|
+
"change-plan",
|
|
21
|
+
"import-plan",
|
|
22
|
+
"risk-summary",
|
|
23
|
+
"canonical-writes",
|
|
24
|
+
"proceed-decision",
|
|
25
|
+
"review-packet",
|
|
26
|
+
"next-action",
|
|
27
|
+
"single-agent-plan",
|
|
28
|
+
"multi-agent-plan",
|
|
29
|
+
"resolved-workflow-context",
|
|
30
|
+
"workflow-preset-activation",
|
|
31
|
+
"workflow-preset-diff",
|
|
32
|
+
"workflow-preset-customization",
|
|
33
|
+
"work-packet",
|
|
34
|
+
"lane-status",
|
|
35
|
+
"handoff-status",
|
|
36
|
+
"auth-hints",
|
|
37
|
+
"auth-review-packet"
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {string[]} args
|
|
42
|
+
* @returns {import("./shared.js").SplitCommandArgs|null}
|
|
43
|
+
*/
|
|
44
|
+
export function parseCoreCommandArgs(args) {
|
|
45
|
+
if (args[0] === "new" || args[0] === "create") {
|
|
46
|
+
return args.includes("--list-templates")
|
|
47
|
+
? { templateCommand: "list", inputPath: null }
|
|
48
|
+
: { newProject: true, inputPath: args[1] };
|
|
49
|
+
}
|
|
50
|
+
if (args[0] === "generate" && args[1] === "app") {
|
|
51
|
+
return { generateTarget: "app-bundle", write: true, inputPath: commandPath(args, 2), defaultOutDir: "./app" };
|
|
52
|
+
}
|
|
53
|
+
if (args[0] === "generate" && args[1] === "journeys") {
|
|
54
|
+
return { workflowName: "generate-journeys", inputPath: args[2] };
|
|
55
|
+
}
|
|
56
|
+
if (args[0] === "generate" && args[1] !== "journeys") {
|
|
57
|
+
return { generateTarget: "app-bundle", write: true, inputPath: commandPath(args, 1), defaultOutDir: "./app" };
|
|
58
|
+
}
|
|
59
|
+
if (args[0] === "emit") {
|
|
60
|
+
if (!args[1] || args[1].startsWith("-")) {
|
|
61
|
+
return { emitHelp: true, inputPath: null };
|
|
62
|
+
}
|
|
63
|
+
return { generateTarget: args[1], inputPath: commandPath(args, 2), emitArtifact: true };
|
|
64
|
+
}
|
|
65
|
+
if (args[0] === "version" || args[0] === "--version") {
|
|
66
|
+
return { version: true, inputPath: null };
|
|
67
|
+
}
|
|
68
|
+
if (args[0] === "query" && args[1] === "list") {
|
|
69
|
+
return { queryList: true, inputPath: null };
|
|
70
|
+
}
|
|
71
|
+
if (args[0] === "query" && args[1] === "show") {
|
|
72
|
+
return { queryShow: true, queryShowName: args[2] || null, inputPath: null };
|
|
73
|
+
}
|
|
74
|
+
if (args[0] === "query" && args[1] === "workflow-preset" && args[2] === "customize") {
|
|
75
|
+
return { workflowPresetCommand: "customize", inputPath: commandPath(args, 3) };
|
|
76
|
+
}
|
|
77
|
+
if (args[0] === "workflow-preset" && args[1] === "customize") {
|
|
78
|
+
return { workflowPresetCommand: "customize", inputPath: commandPath(args, 2) };
|
|
79
|
+
}
|
|
80
|
+
if (args[0] === "query" && QUERY_NAMES.has(args[1])) {
|
|
81
|
+
return { queryName: args[1], inputPath: commandPath(args, 2) };
|
|
82
|
+
}
|
|
83
|
+
if (args[0] === "doctor") {
|
|
84
|
+
return { doctor: true, inputPath: args[1] && !args[1].startsWith("-") ? args[1] : null };
|
|
85
|
+
}
|
|
86
|
+
if (args[0] === "check") {
|
|
87
|
+
return { check: true, inputPath: commandPath(args, 1) };
|
|
88
|
+
}
|
|
89
|
+
if (args[0] === "validate") {
|
|
90
|
+
return { validate: true, inputPath: args[1] };
|
|
91
|
+
}
|
|
92
|
+
if (args[0] === "agent" && args[1] === "brief") {
|
|
93
|
+
return { agentBrief: true, inputPath: commandPath(args, 2) };
|
|
94
|
+
}
|
|
95
|
+
if (args[0] === "widget" && args[1] === "check") {
|
|
96
|
+
return { widgetCheck: true, inputPath: commandPath(args, 2) };
|
|
97
|
+
}
|
|
98
|
+
if (args[0] === "widget" && args[1] === "behavior") {
|
|
99
|
+
return { widgetBehavior: true, inputPath: commandPath(args, 2) };
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { commandPath } from "./shared.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {string[]} args
|
|
7
|
+
* @returns {import("./shared.js").SplitCommandArgs|null}
|
|
8
|
+
*/
|
|
9
|
+
export function parseGeneratorCommandArgs(args) {
|
|
10
|
+
if (args[0] === "generator" && args[1] === "list") {
|
|
11
|
+
return { generatorCommand: "list", inputPath: null };
|
|
12
|
+
}
|
|
13
|
+
if (args[0] === "generator" && args[1] === "show") {
|
|
14
|
+
return { generatorCommand: "show", inputPath: args[2] };
|
|
15
|
+
}
|
|
16
|
+
if (args[0] === "generator" && args[1] === "check") {
|
|
17
|
+
return { generatorCommand: "check", inputPath: args[2] };
|
|
18
|
+
}
|
|
19
|
+
if (args[0] === "generator" && args[1] === "policy" && args[2] === "init") {
|
|
20
|
+
return { generatorPolicyCommand: "init", inputPath: commandPath(args, 3) };
|
|
21
|
+
}
|
|
22
|
+
if (args[0] === "generator" && args[1] === "policy" && args[2] === "status") {
|
|
23
|
+
return { generatorPolicyCommand: "status", inputPath: commandPath(args, 3) };
|
|
24
|
+
}
|
|
25
|
+
if (args[0] === "generator" && args[1] === "policy" && args[2] === "check") {
|
|
26
|
+
return { generatorPolicyCommand: "check", inputPath: commandPath(args, 3) };
|
|
27
|
+
}
|
|
28
|
+
if (args[0] === "generator" && args[1] === "policy" && args[2] === "explain") {
|
|
29
|
+
return { generatorPolicyCommand: "explain", inputPath: commandPath(args, 3) };
|
|
30
|
+
}
|
|
31
|
+
if (args[0] === "generator" && args[1] === "policy" && args[2] === "pin") {
|
|
32
|
+
return {
|
|
33
|
+
generatorPolicyCommand: "pin",
|
|
34
|
+
generatorPolicyPinSpec: args[3] && !args[3].startsWith("-") ? args[3] : null,
|
|
35
|
+
inputPath: commandPath(args, 4)
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { commandOperandFrom, commandPath } from "./shared.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {string[]} args
|
|
7
|
+
* @returns {import("./shared.js").SplitCommandArgs|null}
|
|
8
|
+
*/
|
|
9
|
+
export function parseImportCommandArgs(args) {
|
|
10
|
+
if (args[0] === "import" && args[1] === "app") {
|
|
11
|
+
return { workflowName: "import-app", inputPath: args[2] };
|
|
12
|
+
}
|
|
13
|
+
if (args[0] === "import" && args[1] === "docs") {
|
|
14
|
+
return { workflowName: "scan-docs", inputPath: args[2] };
|
|
15
|
+
}
|
|
16
|
+
if (args[0] === "import" && args[1] === "diff") {
|
|
17
|
+
return { importCommand: "diff", inputPath: commandOperandFrom(args, 2, ".") };
|
|
18
|
+
}
|
|
19
|
+
if (args[0] === "import" && args[1] === "refresh") {
|
|
20
|
+
return { importCommand: "refresh", inputPath: commandOperandFrom(args, 2, ".") };
|
|
21
|
+
}
|
|
22
|
+
if (args[0] === "import" && args[1] === "check") {
|
|
23
|
+
return { importCommand: "check", inputPath: commandPath(args, 2, ".") };
|
|
24
|
+
}
|
|
25
|
+
if (args[0] === "import" && args[1] === "plan") {
|
|
26
|
+
return { importCommand: "plan", inputPath: commandPath(args, 2, ".") };
|
|
27
|
+
}
|
|
28
|
+
if (args[0] === "import" && args[1] === "adopt" && (args[2] === "--list" || args[2] === "list")) {
|
|
29
|
+
return { importCommand: "adopt-list", inputPath: commandPath(args, 3, ".") };
|
|
30
|
+
}
|
|
31
|
+
if (args[0] === "import" && args[1] === "adopt") {
|
|
32
|
+
return { importCommand: "adopt", importAdoptSelector: args[2], inputPath: commandPath(args, 3, ".") };
|
|
33
|
+
}
|
|
34
|
+
if (args[0] === "import" && args[1] === "status") {
|
|
35
|
+
return { importCommand: "status", inputPath: commandPath(args, 2, ".") };
|
|
36
|
+
}
|
|
37
|
+
if (args[0] === "import" && args[1] === "history") {
|
|
38
|
+
return { importCommand: "history", verify: args.includes("--verify"), inputPath: commandOperandFrom(args, 2, ".") };
|
|
39
|
+
}
|
|
40
|
+
if (args[0] === "import" && args[1] && !args[1].startsWith("-")) {
|
|
41
|
+
return { importCommand: "workspace", inputPath: args[1] };
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {string[]} args
|
|
5
|
+
* @returns {import("./shared.js").SplitCommandArgs|null}
|
|
6
|
+
*/
|
|
7
|
+
export function parseLegacyWorkflowCommandArgs(args) {
|
|
8
|
+
if (args[0] === "report" && args[1] === "gaps") {
|
|
9
|
+
return { workflowName: "report-gaps", inputPath: args[2] };
|
|
10
|
+
}
|
|
11
|
+
if (args[0] === "reconcile" && args[1] === "adopt") {
|
|
12
|
+
return { workflowName: "reconcile", inputPath: args[3], adoptValue: args[2] };
|
|
13
|
+
}
|
|
14
|
+
if (args[0] === "reconcile") {
|
|
15
|
+
return { workflowName: "reconcile", inputPath: args[1] };
|
|
16
|
+
}
|
|
17
|
+
if (args[0] === "adoption" && args[1] === "status") {
|
|
18
|
+
return { workflowName: "adoption-status", inputPath: args[2] };
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { commandPath } from "./shared.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {string[]} args
|
|
7
|
+
* @returns {import("./shared.js").SplitCommandArgs|null}
|
|
8
|
+
*/
|
|
9
|
+
export function parseProjectCommandArgs(args) {
|
|
10
|
+
if (args[0] === "source" && args[1] === "status") {
|
|
11
|
+
return { sourceCommand: "status", inputPath: commandPath(args, 2, ".") };
|
|
12
|
+
}
|
|
13
|
+
if (args[0] === "trust" && args[1] === "template") {
|
|
14
|
+
return { trustCommand: "template", force: args.includes("--force"), inputPath: commandPath(args, 2) };
|
|
15
|
+
}
|
|
16
|
+
if (args[0] === "trust" && args[1] === "status") {
|
|
17
|
+
return { trustCommand: "status", inputPath: commandPath(args, 2) };
|
|
18
|
+
}
|
|
19
|
+
if (args[0] === "trust" && args[1] === "diff") {
|
|
20
|
+
return { trustCommand: "diff", inputPath: commandPath(args, 2) };
|
|
21
|
+
}
|
|
22
|
+
if (args[0] === "release" && args[1] === "status") {
|
|
23
|
+
return { releaseCommand: "status", inputPath: null };
|
|
24
|
+
}
|
|
25
|
+
if (args[0] === "release" && args[1] === "roll-consumers") {
|
|
26
|
+
return { releaseCommand: "roll-consumers", releaseRollVersion: args[2], inputPath: null };
|
|
27
|
+
}
|
|
28
|
+
if (args[0] === "catalog" && args[1] === "list") {
|
|
29
|
+
return { catalogCommand: "list", inputPath: args[2] && !args[2].startsWith("-") ? args[2] : null };
|
|
30
|
+
}
|
|
31
|
+
if (args[0] === "catalog" && args[1] === "show") {
|
|
32
|
+
return { catalogCommand: "show", inputPath: args[2] };
|
|
33
|
+
}
|
|
34
|
+
if (args[0] === "catalog" && args[1] === "doctor") {
|
|
35
|
+
return { catalogCommand: "doctor", inputPath: args[2] && !args[2].startsWith("-") ? args[2] : null };
|
|
36
|
+
}
|
|
37
|
+
if (args[0] === "catalog" && args[1] === "check") {
|
|
38
|
+
return { catalogCommand: "check", inputPath: args[2] };
|
|
39
|
+
}
|
|
40
|
+
if (args[0] === "catalog" && args[1] === "copy") {
|
|
41
|
+
return { catalogCommand: "copy", catalogId: args[2], inputPath: args[3] };
|
|
42
|
+
}
|
|
43
|
+
if (args[0] === "package" && args[1] === "update-cli") {
|
|
44
|
+
return { packageCommand: "update-cli", inputPath: args.includes("--latest") ? "latest" : args[2] };
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { commandPath } from "./shared.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {string[]} args
|
|
7
|
+
* @returns {import("./shared.js").SplitCommandArgs|null}
|
|
8
|
+
*/
|
|
9
|
+
export function parseSdlcCommandArgs(args) {
|
|
10
|
+
if (args[0] === "sdlc" && args[1] === "transition") {
|
|
11
|
+
return {
|
|
12
|
+
sdlcCommand: "transition",
|
|
13
|
+
inputPath: args[4] && !args[4].startsWith("-") ? args[4] : ".",
|
|
14
|
+
sdlcId: args[2],
|
|
15
|
+
sdlcTargetStatus: args[3]
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
if (args[0] === "sdlc" && args[1] === "check") {
|
|
19
|
+
return { sdlcCommand: "check", inputPath: commandPath(args, 2, ".") };
|
|
20
|
+
}
|
|
21
|
+
if (args[0] === "sdlc" && args[1] === "explain") {
|
|
22
|
+
return {
|
|
23
|
+
sdlcCommand: "explain",
|
|
24
|
+
sdlcId: args[2],
|
|
25
|
+
inputPath: args[3] && !args[3].startsWith("-") ? args[3] : "."
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
if (args[0] === "sdlc" && args[1] === "archive") {
|
|
29
|
+
return { sdlcCommand: "archive", inputPath: commandPath(args, 2, ".") };
|
|
30
|
+
}
|
|
31
|
+
if (args[0] === "sdlc" && args[1] === "unarchive") {
|
|
32
|
+
return { sdlcCommand: "unarchive", sdlcId: args[2], inputPath: commandPath(args, 3, ".") };
|
|
33
|
+
}
|
|
34
|
+
if (args[0] === "sdlc" && args[1] === "compact") {
|
|
35
|
+
return { sdlcCommand: "compact", sdlcArchiveFile: args[2], inputPath: "." };
|
|
36
|
+
}
|
|
37
|
+
if (args[0] === "sdlc" && args[1] === "new") {
|
|
38
|
+
return { sdlcCommand: "new", sdlcNewKind: args[2], sdlcNewSlug: args[3], inputPath: commandPath(args, 4, ".") };
|
|
39
|
+
}
|
|
40
|
+
if (args[0] === "sdlc" && args[1] === "adopt") {
|
|
41
|
+
return { sdlcCommand: "adopt", inputPath: commandPath(args, 2, ".") };
|
|
42
|
+
}
|
|
43
|
+
if (args[0] === "release") {
|
|
44
|
+
return { sdlcCommand: "release", inputPath: commandPath(args, 1, ".") };
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {Record<string, any> & {
|
|
5
|
+
* inputPath: string|null
|
|
6
|
+
* }} SplitCommandArgs
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {string[]} args
|
|
11
|
+
* @param {number} index
|
|
12
|
+
* @param {string} [fallback]
|
|
13
|
+
* @returns {string}
|
|
14
|
+
*/
|
|
15
|
+
export function commandPath(args, index, fallback = "./topogram") {
|
|
16
|
+
const value = args[index];
|
|
17
|
+
return value && !value.startsWith("-") ? value : fallback;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {string[]} args
|
|
22
|
+
* @param {number} index
|
|
23
|
+
* @param {string} [fallback]
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
26
|
+
export function commandOperandFrom(args, index, fallback = ".") {
|
|
27
|
+
const valueFlags = new Set([
|
|
28
|
+
"--accept-current",
|
|
29
|
+
"--accept-candidate",
|
|
30
|
+
"--delete-current",
|
|
31
|
+
"--from",
|
|
32
|
+
"--out",
|
|
33
|
+
"--out-dir",
|
|
34
|
+
"--reason",
|
|
35
|
+
"--template",
|
|
36
|
+
"--version"
|
|
37
|
+
]);
|
|
38
|
+
for (let i = index; i < args.length; i += 1) {
|
|
39
|
+
const value = args[i];
|
|
40
|
+
if (!value) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (!value.startsWith("-")) {
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
if (valueFlags.has(value)) {
|
|
47
|
+
i += 1;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return fallback;
|
|
51
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { commandPath } from "./shared.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {string[]} args
|
|
7
|
+
* @returns {import("./shared.js").SplitCommandArgs|null}
|
|
8
|
+
*/
|
|
9
|
+
export function parseTemplateCommandArgs(args) {
|
|
10
|
+
if (args[0] === "template" && args[1] === "list") {
|
|
11
|
+
return { templateCommand: "list", inputPath: null };
|
|
12
|
+
}
|
|
13
|
+
if (args[0] === "template" && args[1] === "show") {
|
|
14
|
+
return { templateCommand: "show", inputPath: args[2] };
|
|
15
|
+
}
|
|
16
|
+
if (args[0] === "template" && args[1] === "explain") {
|
|
17
|
+
return { templateCommand: "explain", inputPath: commandPath(args, 2, ".") };
|
|
18
|
+
}
|
|
19
|
+
if (args[0] === "template" && args[1] === "status") {
|
|
20
|
+
return { templateCommand: "status", inputPath: commandPath(args, 2) };
|
|
21
|
+
}
|
|
22
|
+
if (args[0] === "template" && args[1] === "detach") {
|
|
23
|
+
return { templateCommand: "detach", inputPath: commandPath(args, 2, ".") };
|
|
24
|
+
}
|
|
25
|
+
if (args[0] === "template" && args[1] === "policy" && args[2] === "init") {
|
|
26
|
+
return { templateCommand: "policy:init", inputPath: commandPath(args, 3) };
|
|
27
|
+
}
|
|
28
|
+
if (args[0] === "template" && args[1] === "policy" && args[2] === "check") {
|
|
29
|
+
return { templateCommand: "policy:check", inputPath: commandPath(args, 3) };
|
|
30
|
+
}
|
|
31
|
+
if (args[0] === "template" && args[1] === "policy" && args[2] === "explain") {
|
|
32
|
+
return { templateCommand: "policy:explain", inputPath: commandPath(args, 3) };
|
|
33
|
+
}
|
|
34
|
+
if (args[0] === "template" && args[1] === "policy" && args[2] === "pin") {
|
|
35
|
+
return {
|
|
36
|
+
templateCommand: "policy:pin",
|
|
37
|
+
templatePolicyPinSpec: args[3] && !args[3].startsWith("-") ? args[3] : null,
|
|
38
|
+
inputPath: commandPath(args, 4)
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (args[0] === "template" && args[1] === "check") {
|
|
42
|
+
return { templateCommand: "check", inputPath: args[2] };
|
|
43
|
+
}
|
|
44
|
+
if (args[0] === "template" && args[1] === "update") {
|
|
45
|
+
return { templateCommand: "update", inputPath: commandPath(args, 2) };
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { buildAgentBrief, formatAgentBrief, normalizeAgentTopogramPath } from "../../agent-brief.js";
|
|
4
|
+
import { stableStringify } from "../../format.js";
|
|
5
|
+
import { parsePath } from "../../parser.js";
|
|
6
|
+
import { formatProjectConfigErrors } from "../../project-config.js";
|
|
7
|
+
import { formatValidationErrors } from "../../validator.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @returns {void}
|
|
11
|
+
*/
|
|
12
|
+
export function printAgentHelp() {
|
|
13
|
+
console.log("Usage: topogram agent brief [path] [--json]");
|
|
14
|
+
console.log("");
|
|
15
|
+
console.log("Prints read-only first-run guidance for humans and agents working in a Topogram project.");
|
|
16
|
+
console.log("");
|
|
17
|
+
console.log("Defaults: path is ./topogram. The command validates the Topogram and project config, but does not write files, generate apps, load generator adapters, or execute template implementation.");
|
|
18
|
+
console.log("");
|
|
19
|
+
console.log("Examples:");
|
|
20
|
+
console.log(" topogram agent brief");
|
|
21
|
+
console.log(" topogram agent brief --json");
|
|
22
|
+
console.log(" topogram agent brief ./topogram --json");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {string} inputPath
|
|
27
|
+
* @param {{ json?: boolean }} [options]
|
|
28
|
+
* @returns {number}
|
|
29
|
+
*/
|
|
30
|
+
export function runAgentBriefCommand(inputPath, options = {}) {
|
|
31
|
+
const ast = parsePath(normalizeAgentTopogramPath(inputPath));
|
|
32
|
+
const result = buildAgentBrief(inputPath, ast);
|
|
33
|
+
if (!result.ok) {
|
|
34
|
+
if (result.kind === "project") {
|
|
35
|
+
console.error(formatProjectConfigErrors(result.validation, result.configPath));
|
|
36
|
+
} else {
|
|
37
|
+
console.error(formatValidationErrors(result.validation));
|
|
38
|
+
}
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
|
41
|
+
if (options.json) {
|
|
42
|
+
console.log(stableStringify(result.payload));
|
|
43
|
+
} else {
|
|
44
|
+
process.stdout.write(formatAgentBrief(result.payload));
|
|
45
|
+
}
|
|
46
|
+
return 0;
|
|
47
|
+
}
|