315 lines
12 KiB
TypeScript
315 lines
12 KiB
TypeScript
import type {
|
|
ChangeDocSyncInput,
|
|
DeployObservationInput,
|
|
GlobalCodeReviewInput,
|
|
NapcatRemoteDevHandoffInput,
|
|
NapcatRuntimeReleaseReadinessInput,
|
|
NapcatSyncCandidateReviewInput,
|
|
NasCodexBootstrapPlanInput,
|
|
ObsidianContextInput,
|
|
ObsidianSyncInput,
|
|
ObsidianValidateInput,
|
|
WorkstreamCloseoutInput,
|
|
WorkstreamReusablePattern,
|
|
} from '../types.js';
|
|
import { workstreamReusablePatternValues } from './constants.js';
|
|
import type { NapcatUpstreamAuditInput } from '../tools/napcatAutomation.types.js';
|
|
|
|
function parseContentScanMode(argv: string[]): 'all' | 'changed' {
|
|
if (argv.includes('--content-scan-all') || argv.includes('--contentScanMode=all')) {
|
|
return 'all';
|
|
}
|
|
|
|
const modeIndex = argv.findIndex(
|
|
(item) => item === '--contentScanMode' || item === '--content-scan-mode',
|
|
);
|
|
return modeIndex >= 0 && argv[modeIndex + 1] === 'all' ? 'all' : 'changed';
|
|
}
|
|
|
|
export function parseGlobalReviewCliArgs(
|
|
argv: string[],
|
|
): Pick<GlobalCodeReviewInput, 'contentScanMode' | 'includeContentScan' | 'includeRootScan'> {
|
|
return {
|
|
contentScanMode: parseContentScanMode(argv),
|
|
includeContentScan: !argv.includes('--no-content-scan'),
|
|
includeRootScan: !argv.includes('--no-root-scan'),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Parses the change documentation sync CLI mode into the same input shape as the MCP tool.
|
|
* @param argv - Full process argv including `--change-doc-sync` and optional project/file flags.
|
|
* @returns Project key plus explicitly supplied changed files; absent files make the tool infer git status.
|
|
*/
|
|
export function parseChangeDocSyncCliArgs(argv: string[]): ChangeDocSyncInput {
|
|
return {
|
|
changedFiles: readOptionList(argv, [
|
|
'--changed-file',
|
|
'--changed-files',
|
|
'--file',
|
|
'--files',
|
|
]),
|
|
project: readOption(argv, ['--project']),
|
|
};
|
|
}
|
|
|
|
function readOption(argv: string[], names: string[]): string | undefined {
|
|
for (const name of names) {
|
|
const keyValue = argv.find((item) => item.startsWith(`${name}=`));
|
|
if (keyValue) return keyValue.slice(name.length + 1);
|
|
|
|
const index = argv.indexOf(name);
|
|
if (index >= 0 && argv[index + 1] && !argv[index + 1].startsWith('--')) {
|
|
return argv[index + 1];
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function readOptionList(argv: string[], names: string[]): string[] {
|
|
const values: string[] = [];
|
|
|
|
names.forEach((name) => {
|
|
argv.forEach((item, index) => {
|
|
if (item.startsWith(`${name}=`)) {
|
|
values.push(item.slice(name.length + 1));
|
|
} else if (item === name && argv[index + 1] && !argv[index + 1].startsWith('--')) {
|
|
values.push(argv[index + 1]);
|
|
}
|
|
});
|
|
});
|
|
|
|
return values
|
|
.flatMap((value) => value.split(','))
|
|
.map((value) => value.trim())
|
|
.filter(Boolean);
|
|
}
|
|
|
|
/**
|
|
* Checks whether any supported boolean CLI flag is present in the raw argv list.
|
|
* @param argv - Full process argv including node, script, mode flag, and user options.
|
|
* @param names - Flag names to accept in dashed or camelCase form.
|
|
* @returns True when the caller provided one of the named flags.
|
|
*/
|
|
function hasFlag(argv: string[], names: string[]): boolean {
|
|
return names.some((name) => argv.includes(name));
|
|
}
|
|
|
|
export function parseWorkstreamCloseoutCliArgs(argv: string[]): WorkstreamCloseoutInput {
|
|
const patternSet = new Set<string>(workstreamReusablePatternValues);
|
|
const patterns = readOptionList(argv, ['--pattern', '--reusable-pattern'])
|
|
.filter((pattern): pattern is WorkstreamReusablePattern => patternSet.has(pattern));
|
|
const cleanupFinalDeleted = Number(
|
|
readOption(argv, ['--cleanup-final-deleted', '--cleanupFinalDeleted']),
|
|
);
|
|
|
|
return {
|
|
changedProjects: readOptionList(argv, ['--project', '--changed-project']),
|
|
cleanupEvidence: readOptionList(argv, ['--cleanup', '--cleanup-evidence']),
|
|
cleanupFinalDeleted: Number.isFinite(cleanupFinalDeleted) ? cleanupFinalDeleted : undefined,
|
|
docSyncEvidence: readOptionList(argv, ['--doc-sync', '--doc-sync-evidence']),
|
|
ktWorkflowUpdated: argv.includes('--kt-workflow-updated'),
|
|
problemRecords: readOptionList(argv, ['--problem', '--problem-record']),
|
|
reviewEvidence: readOptionList(argv, ['--review', '--review-evidence']),
|
|
reusablePatterns: patterns,
|
|
stableSolutions: readOptionList(argv, ['--solution', '--stable-solution']),
|
|
superpowersReviewEvidence: readOptionList(argv, [
|
|
'--superpowers-review',
|
|
'--superpowers-review-evidence',
|
|
'--sp-review',
|
|
]),
|
|
title: readOption(argv, ['--title']),
|
|
upgradeNotes: readOption(argv, ['--upgrade-notes']),
|
|
verificationEvidence: readOptionList(argv, ['--verification', '--verification-evidence']),
|
|
};
|
|
}
|
|
|
|
export function parseDeployObservationCliArgs(argv: string[]): DeployObservationInput {
|
|
const sshPort = Number(readOption(argv, ['--ssh-port', '--sshPort']));
|
|
|
|
return {
|
|
artifactRoot: readOption(argv, ['--artifact-root', '--artifactRoot']),
|
|
buildNumber: readOption(argv, ['--build', '--build-number', '--buildNumber']),
|
|
container: readOption(argv, ['--container']),
|
|
deployment: readOption(argv, ['--deployment']),
|
|
execute: argv.includes('--execute'),
|
|
expectedCommit: readOption(argv, [
|
|
'--commit',
|
|
'--expected-commit',
|
|
'--expectedCommit',
|
|
]),
|
|
healthUrl: readOption(argv, ['--health-url', '--healthUrl']),
|
|
imageTag: readOption(argv, ['--image-tag', '--imageTag']),
|
|
jenkinsHome: readOption(argv, ['--jenkins-home', '--jenkinsHome']),
|
|
jobName: readOption(argv, ['--job', '--job-name', '--jobName']),
|
|
kubeconfigPath: readOption(argv, [
|
|
'--kubeconfig',
|
|
'--kubeconfig-path',
|
|
'--kubeconfigPath',
|
|
]),
|
|
namespace: readOption(argv, ['--namespace']),
|
|
project: readOption(argv, ['--project']),
|
|
selector: readOption(argv, ['--selector']),
|
|
smoke: readOption(argv, ['--smoke']),
|
|
sshPort: Number.isFinite(sshPort) ? sshPort : undefined,
|
|
sshTarget: readOption(argv, ['--ssh', '--ssh-target', '--sshTarget']),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Parses the NapCat upstream audit CLI mode into the deterministic audit builder input.
|
|
* @param argv - Full process argv from `src/server.ts`; `--execute` and `--use-codex` must both be explicit before Codex can run.
|
|
* @returns Upstream audit input with dry-run defaults for all optional metadata lists.
|
|
*/
|
|
export function parseNapcatUpstreamAuditCliArgs(
|
|
argv: string[],
|
|
): NapcatUpstreamAuditInput {
|
|
return {
|
|
artifactRoot: readOption(argv, ['--artifact-root', '--artifactRoot']),
|
|
codexHome: readOption(argv, ['--codex-home', '--codexHome']),
|
|
createCandidateBranch: hasFlag(argv, ['--create-candidate-branch', '--createCandidateBranch']),
|
|
dryMergeConflict: hasFlag(argv, ['--dry-merge-conflict', '--dryMergeConflict']),
|
|
execute: hasFlag(argv, ['--execute']),
|
|
forkBranch: readOption(argv, ['--fork-branch', '--forkBranch']),
|
|
forkPatchFiles: readOptionList(argv, [
|
|
'--fork-patch-file',
|
|
'--fork-patch-files',
|
|
'--forkPatchFile',
|
|
'--forkPatchFiles',
|
|
]),
|
|
forkRepo: readOption(argv, ['--fork-repo', '--forkRepo']),
|
|
lastAcceptedUpstreamBase: readOption(argv, [
|
|
'--last-accepted-upstream-base',
|
|
'--lastAcceptedUpstreamBase',
|
|
]),
|
|
outputSchemaPath: readOption(argv, ['--output-schema', '--outputSchemaPath']),
|
|
upstreamChangedFiles: readOptionList(argv, [
|
|
'--upstream-changed-file',
|
|
'--upstream-changed-files',
|
|
'--upstreamChangedFile',
|
|
'--upstreamChangedFiles',
|
|
]),
|
|
upstreamReleaseTag: readOption(argv, [
|
|
'--upstream-release-tag',
|
|
'--upstream-release-ref',
|
|
'--upstreamReleaseTag',
|
|
]),
|
|
upstreamRepo: readOption(argv, ['--upstream-repo', '--upstreamRepo']),
|
|
useCodex: hasFlag(argv, ['--use-codex', '--useCodex']),
|
|
workspaceRoot: readOption(argv, ['--workspace-root', '--workspaceRoot']),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Parses common dry-run NapCat prompt tool options shared by candidate review, readiness, and handoff modes.
|
|
* @param argv - Full process argv from a NapCat automation CLI script.
|
|
* @returns Prompt skeleton input with `execute` false unless explicitly requested by the caller.
|
|
*/
|
|
function parseNapcatPromptToolCliArgs(argv: string[]): NapcatSyncCandidateReviewInput {
|
|
return {
|
|
artifactRoot: readOption(argv, ['--artifact-root', '--artifactRoot']),
|
|
candidateBranch: readOption(argv, ['--candidate-branch', '--candidateBranch']),
|
|
execute: hasFlag(argv, ['--execute']),
|
|
forkBranch: readOption(argv, ['--fork-branch', '--forkBranch']),
|
|
forkRepo: readOption(argv, ['--fork-repo', '--forkRepo']),
|
|
lastAcceptedUpstreamBase: readOption(argv, [
|
|
'--last-accepted-upstream-base',
|
|
'--lastAcceptedUpstreamBase',
|
|
]),
|
|
upstreamReleaseTag: readOption(argv, [
|
|
'--upstream-release-tag',
|
|
'--upstream-release-ref',
|
|
'--upstreamReleaseTag',
|
|
]),
|
|
upstreamRepo: readOption(argv, ['--upstream-repo', '--upstreamRepo']),
|
|
useCodex: hasFlag(argv, ['--use-codex', '--useCodex']),
|
|
workspaceRoot: readOption(argv, ['--workspace-root', '--workspaceRoot']),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Parses the NapCat sync-candidate review CLI mode.
|
|
* @param argv - Full process argv from `pnpm run napcat-sync-candidate-review`.
|
|
* @returns Dry-run prompt skeleton input for human/Codex candidate review handoff.
|
|
*/
|
|
export function parseNapcatSyncCandidateReviewCliArgs(
|
|
argv: string[],
|
|
): NapcatSyncCandidateReviewInput {
|
|
return parseNapcatPromptToolCliArgs(argv);
|
|
}
|
|
|
|
/**
|
|
* Parses the NapCat runtime release readiness CLI mode.
|
|
* @param argv - Full process argv from `pnpm run napcat-runtime-release-readiness`.
|
|
* @returns Dry-run prompt skeleton input with image/profile hints and no default mutation.
|
|
*/
|
|
export function parseNapcatRuntimeReleaseReadinessCliArgs(
|
|
argv: string[],
|
|
): NapcatRuntimeReleaseReadinessInput {
|
|
return {
|
|
...parseNapcatPromptToolCliArgs(argv),
|
|
apiImageTag: readOption(argv, ['--api-image-tag', '--apiImageTag']),
|
|
napcatImageTag: readOption(argv, ['--napcat-image-tag', '--napcatImageTag']),
|
|
profile: readOption(argv, ['--profile']),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Parses the NapCat remote development handoff CLI mode.
|
|
* @param argv - Full process argv from `src/server.ts --napcat-remote-dev-handoff`.
|
|
* @returns Dry-run prompt skeleton input for a remote workspace handoff packet.
|
|
*/
|
|
export function parseNapcatRemoteDevHandoffCliArgs(
|
|
argv: string[],
|
|
): NapcatRemoteDevHandoffInput {
|
|
return {
|
|
...parseNapcatPromptToolCliArgs(argv),
|
|
targetHost: readOption(argv, ['--target-host', '--targetHost']),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Parses the NAS Codex bootstrap planning CLI mode.
|
|
* @param argv - Full process argv from `pnpm run nas-codex-bootstrap`.
|
|
* @returns Dry-run bootstrap plan input; `execute` is false unless the caller explicitly opts in.
|
|
*/
|
|
export function parseNasCodexBootstrapCliArgs(
|
|
argv: string[],
|
|
): NasCodexBootstrapPlanInput {
|
|
return {
|
|
artifactRoot: readOption(argv, ['--artifact-root', '--artifactRoot']),
|
|
codexHome: readOption(argv, ['--codex-home', '--codexHome']),
|
|
execute: hasFlag(argv, ['--execute']),
|
|
serviceName: readOption(argv, ['--service-name', '--serviceName']),
|
|
workspaceRoot: readOption(argv, ['--workspace-root', '--workspaceRoot']),
|
|
};
|
|
}
|
|
|
|
export function parseObsidianCliArgs(argv: string[]): ObsidianValidateInput {
|
|
return {
|
|
checkLayout: !argv.includes('--no-layout'),
|
|
failOnWarnings: argv.includes('--fail-on-warnings'),
|
|
includeMarkdownLinks: !argv.includes('--no-markdown-links'),
|
|
includeStaleReferences: !argv.includes('--no-stale-references'),
|
|
};
|
|
}
|
|
|
|
export function parseObsidianSyncCliArgs(argv: string[]): ObsidianSyncInput {
|
|
return {
|
|
...parseObsidianCliArgs(argv),
|
|
includeValidation: !argv.includes('--no-validation'),
|
|
};
|
|
}
|
|
|
|
export function parseObsidianContextCliArgs(argv: string[]): ObsidianContextInput {
|
|
const maxDocuments = Number(readOption(argv, ['--max-documents', '--maxDocuments']));
|
|
|
|
return {
|
|
includeCanvas: !argv.includes('--no-canvas'),
|
|
includeContent: !argv.includes('--no-content'),
|
|
maxDocuments: Number.isFinite(maxDocuments) ? maxDocuments : undefined,
|
|
module: readOption(argv, ['--module']),
|
|
query: readOption(argv, ['--query']),
|
|
};
|
|
}
|