@klhapp/skillmux 1.12.0 → 1.13.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/CHANGELOG.md +8 -0
- package/README.md +18 -9
- package/bin/skillmux.js +89 -0
- package/docs/README.md +4 -4
- package/docs/cli.md +14 -4
- package/docs/concepts.md +2 -2
- package/docs/configuration.md +18 -3
- package/docs/deployment.md +3 -3
- package/docs/getting-started.md +32 -7
- package/docs/releasing.md +17 -2
- package/docs/sdd/cli-remote-target-parity/schema-bucket-b.yaml +297 -0
- package/docs/sdd/cli-remote-target-parity/spec-bucket-b.md +95 -0
- package/docs/sdd/cli-remote-target-parity/spec.md +111 -0
- package/docs/sdd/cli-remote-target-parity/think.md +183 -0
- package/docs/sdd/cli-surface-consistency/think.md +149 -0
- package/docs/sdd/config-authority-rerank-resilience/plan.md +338 -0
- package/docs/sdd/config-authority-rerank-resilience/review.md +124 -0
- package/docs/sdd/config-authority-rerank-resilience/spec.md +78 -0
- package/docs/sdd/feat-packaging-deployment/review.md +95 -0
- package/docs/sdd/fetch-outcome-flywheel/review.md +103 -0
- package/docs/sdd/fetch-outcome-flywheel/schema.json +347 -0
- package/docs/sdd/fetch-outcome-flywheel/spec.md +185 -0
- package/docs/sdd/npm-native-binaries/review.md +103 -0
- package/docs/sdd/npm-native-binaries/spec.md +148 -0
- package/docs/sdd/ranked-shortlist-2/pr3-calibration-removal.md +56 -0
- package/docs/sdd/refactor-builtin-target-paths/spec.md +41 -0
- package/docs/sdd/runtime-resource-hardening/review.md +86 -0
- package/docs/sdd/runtime-resource-hardening/spec.md +43 -0
- package/docs/sdd/security-hardening-phase/think.md +129 -0
- package/docs/sdd/security-observability/review.md +96 -0
- package/docs/sdd/security-observability/spec.md +55 -0
- package/docs/sdd/skill-provenance-update/review.md +152 -0
- package/docs/sdd/skill-provenance-update/schema.json +193 -0
- package/docs/sdd/skill-provenance-update/spec.md +134 -0
- package/docs/sdd/supply-chain-hardening/review.md +50 -0
- package/docs/sdd/supply-chain-hardening/spec.md +42 -0
- package/docs/sdd/target-marker-rehome/spec.md +33 -0
- package/docs/troubleshooting.md +18 -2
- package/package.json +15 -8
- package/src/adapters.ts +0 -438
- package/src/audit.ts +0 -21
- package/src/cli.ts +0 -928
- package/src/clients.ts +0 -368
- package/src/commands/audit.ts +0 -85
- package/src/commands/config.ts +0 -224
- package/src/commands/context.ts +0 -104
- package/src/commands/core.ts +0 -56
- package/src/commands/doctor.ts +0 -97
- package/src/commands/eval.ts +0 -88
- package/src/commands/init.ts +0 -672
- package/src/commands/install.ts +0 -155
- package/src/commands/local-vault.ts +0 -60
- package/src/commands/models.ts +0 -10
- package/src/commands/outdated.ts +0 -115
- package/src/commands/project.ts +0 -580
- package/src/commands/report.ts +0 -66
- package/src/commands/scan.ts +0 -71
- package/src/commands/shared.ts +0 -38
- package/src/commands/skill.ts +0 -33
- package/src/commands/sync.ts +0 -233
- package/src/commands/target.ts +0 -225
- package/src/commands/update.ts +0 -277
- package/src/completions.ts +0 -196
- package/src/concurrency-limiter.ts +0 -61
- package/src/config-service.ts +0 -398
- package/src/config-watcher.ts +0 -253
- package/src/config.ts +0 -583
- package/src/context.ts +0 -188
- package/src/db-audit.ts +0 -286
- package/src/db-index.ts +0 -238
- package/src/db.ts +0 -3
- package/src/deployment.ts +0 -39
- package/src/doctor.ts +0 -205
- package/src/eval.ts +0 -302
- package/src/global-flags.ts +0 -46
- package/src/init-agents.ts +0 -329
- package/src/init-instructions.ts +0 -192
- package/src/init.ts +0 -390
- package/src/install.ts +0 -232
- package/src/lifecycle.ts +0 -51
- package/src/logger.ts +0 -26
- package/src/manifest.ts +0 -359
- package/src/mcp-registration.ts +0 -89
- package/src/metrics.ts +0 -121
- package/src/models.ts +0 -20
- package/src/output.ts +0 -214
- package/src/project-setup.ts +0 -36
- package/src/prompts.ts +0 -124
- package/src/provenance.ts +0 -99
- package/src/rate-limiter.ts +0 -137
- package/src/readiness.ts +0 -30
- package/src/redact.ts +0 -52
- package/src/router-core.ts +0 -666
- package/src/rrf.ts +0 -31
- package/src/scan.ts +0 -300
- package/src/server.ts +0 -930
- package/src/setup.ts +0 -145
- package/src/snapshot.ts +0 -135
- package/src/stats.ts +0 -340
- package/src/sync.ts +0 -482
- package/src/toml-writer.ts +0 -51
- package/src/types.ts +0 -212
- package/src/vault.ts +0 -207
package/src/commands/context.ts
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
addContext,
|
|
3
|
-
getCurrentContext,
|
|
4
|
-
listContexts,
|
|
5
|
-
removeContext,
|
|
6
|
-
useContext,
|
|
7
|
-
type ResolvedContext,
|
|
8
|
-
} from "../context";
|
|
9
|
-
import {
|
|
10
|
-
emitSuccess,
|
|
11
|
-
renderTable,
|
|
12
|
-
renderContextBanner,
|
|
13
|
-
unknownSubcommandError,
|
|
14
|
-
} from "../output";
|
|
15
|
-
|
|
16
|
-
export async function handleContextCommand(
|
|
17
|
-
sub: string,
|
|
18
|
-
args: string[],
|
|
19
|
-
ctx: { context: ResolvedContext; isJson: boolean },
|
|
20
|
-
) {
|
|
21
|
-
if (sub === "list") {
|
|
22
|
-
const contexts = await listContexts();
|
|
23
|
-
emitSuccess({ isJson: ctx.isJson, context: ctx.context }, contexts, () => {
|
|
24
|
-
renderContextBanner(ctx.context);
|
|
25
|
-
renderTable(
|
|
26
|
-
[
|
|
27
|
-
{ key: "name", header: "NAME" },
|
|
28
|
-
{ key: "server", header: "SERVER" },
|
|
29
|
-
{ key: "token_env", header: "TOKEN_ENV" },
|
|
30
|
-
{ key: "isDefault", header: "DEFAULT" },
|
|
31
|
-
],
|
|
32
|
-
contexts.map((c) => ({
|
|
33
|
-
...c,
|
|
34
|
-
token_env: c.token_env ?? "-",
|
|
35
|
-
isDefault: c.isDefault ? "*" : "",
|
|
36
|
-
})),
|
|
37
|
-
);
|
|
38
|
-
});
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (sub === "current") {
|
|
43
|
-
const current = await getCurrentContext();
|
|
44
|
-
emitSuccess({ isJson: ctx.isJson, context: ctx.context }, current, () => {
|
|
45
|
-
renderContextBanner(ctx.context);
|
|
46
|
-
console.log(`Current context: ${current.name} (${current.server})`);
|
|
47
|
-
});
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (sub === "add") {
|
|
52
|
-
const name = args[0];
|
|
53
|
-
let server: string | undefined;
|
|
54
|
-
let tokenEnv: string | undefined;
|
|
55
|
-
for (let i = 1; i < args.length; i++) {
|
|
56
|
-
if (args[i] === "--server") server = args[++i];
|
|
57
|
-
else if (args[i] === "--token-env") tokenEnv = args[++i];
|
|
58
|
-
}
|
|
59
|
-
if (!name || !server) {
|
|
60
|
-
throw new Error(
|
|
61
|
-
"usage: skillmux context add <name> --server <url> [--token-env <env_name>]",
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
await addContext(name, { server, token_env: tokenEnv });
|
|
65
|
-
emitSuccess(
|
|
66
|
-
{ isJson: ctx.isJson, context: ctx.context },
|
|
67
|
-
{ name, server, token_env: tokenEnv },
|
|
68
|
-
() => {
|
|
69
|
-
console.log(`Added context "${name}" -> ${server}`);
|
|
70
|
-
},
|
|
71
|
-
);
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (sub === "use") {
|
|
76
|
-
const name = args[0];
|
|
77
|
-
if (!name) throw new Error("usage: skillmux context use <name>");
|
|
78
|
-
await useContext(name);
|
|
79
|
-
emitSuccess(
|
|
80
|
-
{ isJson: ctx.isJson, context: ctx.context },
|
|
81
|
-
{ default_context: name },
|
|
82
|
-
() => {
|
|
83
|
-
console.log(`Switched default context to "${name}"`);
|
|
84
|
-
},
|
|
85
|
-
);
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (sub === "remove") {
|
|
90
|
-
const name = args[0];
|
|
91
|
-
if (!name) throw new Error("usage: skillmux context remove <name>");
|
|
92
|
-
await removeContext(name);
|
|
93
|
-
emitSuccess(
|
|
94
|
-
{ isJson: ctx.isJson, context: ctx.context },
|
|
95
|
-
{ removed: name },
|
|
96
|
-
() => {
|
|
97
|
-
console.log(`Removed context "${name}"`);
|
|
98
|
-
},
|
|
99
|
-
);
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
throw unknownSubcommandError("context", sub, ["add", "list", "current", "use", "remove"]);
|
|
104
|
-
}
|
package/src/commands/core.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { expandHome } from "../config";
|
|
2
|
-
import { pinCore, unpinCore, validateManifest, writeManifestAtomic } from "../manifest";
|
|
3
|
-
import { emitSuccess, unknownSubcommandError } from "../output";
|
|
4
|
-
import { confirmIfNeeded, loadManifestContext } from "./shared";
|
|
5
|
-
export async function runCore(
|
|
6
|
-
subCommand: string,
|
|
7
|
-
args: string[],
|
|
8
|
-
options: { isJson: boolean; dryRun: boolean },
|
|
9
|
-
): Promise<void> {
|
|
10
|
-
if (subCommand !== "pin" && subCommand !== "unpin") {
|
|
11
|
-
throw unknownSubcommandError("core", subCommand, ["pin", "unpin"]);
|
|
12
|
-
}
|
|
13
|
-
const skillIds = args.filter((arg) => !arg.startsWith("-"));
|
|
14
|
-
if (skillIds.length === 0) {
|
|
15
|
-
throw new Error(`usage: skillmux core ${subCommand} <skill_id>... --yes`);
|
|
16
|
-
}
|
|
17
|
-
const yes = args.includes("--yes");
|
|
18
|
-
const { config, vaultPath, manifestPath, manifest } =
|
|
19
|
-
await loadManifestContext();
|
|
20
|
-
let updated = manifest;
|
|
21
|
-
for (const skillId of skillIds) {
|
|
22
|
-
updated =
|
|
23
|
-
subCommand === "pin"
|
|
24
|
-
? pinCore(updated, skillId)
|
|
25
|
-
: unpinCore(updated, skillId);
|
|
26
|
-
}
|
|
27
|
-
validateManifest(
|
|
28
|
-
updated,
|
|
29
|
-
vaultPath,
|
|
30
|
-
config.local_vault_paths.map(expandHome),
|
|
31
|
-
);
|
|
32
|
-
if (options.dryRun) {
|
|
33
|
-
emitSuccess(
|
|
34
|
-
{ isJson: options.isJson },
|
|
35
|
-
{ subcommand: subCommand, skill_ids: skillIds },
|
|
36
|
-
() =>
|
|
37
|
-
console.log(`${subCommand}: [core] ${skillIds.join(", ")} (dry-run)`),
|
|
38
|
-
);
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
if (
|
|
42
|
-
!(await confirmIfNeeded({
|
|
43
|
-
confirmed: yes,
|
|
44
|
-
isJson: options.isJson,
|
|
45
|
-
prompt: `${subCommand} ${skillIds.join(", ")} in [core]?`,
|
|
46
|
-
nonInteractiveError: `skillmux core ${subCommand} requires --yes when run non-interactively`,
|
|
47
|
-
}))
|
|
48
|
-
)
|
|
49
|
-
return;
|
|
50
|
-
writeManifestAtomic(manifestPath, updated);
|
|
51
|
-
emitSuccess(
|
|
52
|
-
{ isJson: options.isJson },
|
|
53
|
-
{ subcommand: subCommand, skill_ids: skillIds },
|
|
54
|
-
() => console.log(`${subCommand}: [core] ${skillIds.join(", ")}`),
|
|
55
|
-
);
|
|
56
|
-
}
|
package/src/commands/doctor.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { resolveConfigPath } from "../config";
|
|
2
|
-
import { diagnose } from "../doctor";
|
|
3
|
-
import { getEffectiveConfig } from "../config-service";
|
|
4
|
-
import type { ContextAdapter } from "../adapters";
|
|
5
|
-
import type { ResolvedContext } from "../context";
|
|
6
|
-
import { isGlobalFlag, isGlobalFlagWithValue } from "../global-flags";
|
|
7
|
-
import {
|
|
8
|
-
emitSuccess,
|
|
9
|
-
green,
|
|
10
|
-
red,
|
|
11
|
-
renderContextBanner,
|
|
12
|
-
} from "../output";
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* doctor takes no options of its own, but it still has to reject unknown ones
|
|
16
|
-
* rather than silently ignoring them the way every other command does.
|
|
17
|
-
*/
|
|
18
|
-
export function parseDoctorArgs(args: readonly string[]): void {
|
|
19
|
-
for (let i = 0; i < args.length; i++) {
|
|
20
|
-
const option = args[i];
|
|
21
|
-
if (isGlobalFlag(option, "--json", "--allow-insecure", "--verbose")) {
|
|
22
|
-
// handled globally by main(); recognized here so it isn't rejected
|
|
23
|
-
} else if (isGlobalFlagWithValue(option)) {
|
|
24
|
-
// handled globally by main()'s resolveContext(); skip its value too
|
|
25
|
-
i++;
|
|
26
|
-
} else {
|
|
27
|
-
throw new Error(`unknown doctor option: ${option}`);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export async function runDoctor(options: {
|
|
33
|
-
isJson: boolean;
|
|
34
|
-
context: ResolvedContext;
|
|
35
|
-
adapter: ContextAdapter;
|
|
36
|
-
args?: readonly string[];
|
|
37
|
-
}): Promise<void> {
|
|
38
|
-
parseDoctorArgs(options.args ?? []);
|
|
39
|
-
if (options.context.type === "remote") {
|
|
40
|
-
const context = options.context;
|
|
41
|
-
const [status, caps] = await Promise.all([
|
|
42
|
-
options.adapter.configStatus(),
|
|
43
|
-
options.adapter.getCapabilities(),
|
|
44
|
-
]);
|
|
45
|
-
const remoteReport = {
|
|
46
|
-
target: context.name || context.server,
|
|
47
|
-
server: context.server,
|
|
48
|
-
version: status.version,
|
|
49
|
-
deployment_runtime: status.deployment_runtime,
|
|
50
|
-
image_variant: status.image_variant ?? null,
|
|
51
|
-
runtime: status.runtime,
|
|
52
|
-
readiness: status.readiness,
|
|
53
|
-
active_revision: status.active_revision,
|
|
54
|
-
capabilities: caps,
|
|
55
|
-
restart_required_keys: status.restart_required_keys,
|
|
56
|
-
last_reload_error: status.last_reload_error,
|
|
57
|
-
};
|
|
58
|
-
emitSuccess({ isJson: options.isJson, target: options.context }, remoteReport, () => {
|
|
59
|
-
renderContextBanner(options.context);
|
|
60
|
-
console.log(`server: ${remoteReport.server}`);
|
|
61
|
-
console.log(`version: ${remoteReport.version}`);
|
|
62
|
-
console.log(`deployment runtime: ${remoteReport.deployment_runtime}`);
|
|
63
|
-
console.log(`image variant: ${remoteReport.image_variant ?? "none"}`);
|
|
64
|
-
console.log(`runtime: ${remoteReport.runtime}`);
|
|
65
|
-
console.log(`readiness: ${remoteReport.readiness.status} (${remoteReport.readiness.capability})`);
|
|
66
|
-
console.log(`active revision: ${remoteReport.active_revision}`);
|
|
67
|
-
console.log(`persistence: ${caps.persistence}`);
|
|
68
|
-
console.log(`config read: ${caps.config_read}`);
|
|
69
|
-
console.log(`config write: ${caps.config_write}`);
|
|
70
|
-
if (status.last_reload_error) {
|
|
71
|
-
console.log(`last reload error: ${status.last_reload_error}`);
|
|
72
|
-
}
|
|
73
|
-
if (status.restart_required_keys.length > 0) {
|
|
74
|
-
console.log(`restart required for: ${status.restart_required_keys.join(", ")}`);
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const effective = await getEffectiveConfig(resolveConfigPath());
|
|
81
|
-
const report = await diagnose(effective.effective, process.env, effective.sources);
|
|
82
|
-
emitSuccess({ isJson: options.isJson }, report, () => {
|
|
83
|
-
console.log(`version: ${report.version}`);
|
|
84
|
-
console.log(`runtime: ${report.runtime}`);
|
|
85
|
-
console.log(`image variant: ${report.image_variant ?? "none"}`);
|
|
86
|
-
console.log(`vault path: ${report.vault_path}`);
|
|
87
|
-
console.log(`state directory: ${report.state_dir}`);
|
|
88
|
-
console.log(`inference mode: ${report.mode}`);
|
|
89
|
-
console.log(`routing capability: ${report.capability}`);
|
|
90
|
-
console.log(`retrieval capability: ${report.retrieval_capability}`);
|
|
91
|
-
for (const check of report.checks)
|
|
92
|
-
console.log(
|
|
93
|
-
`${check.ok ? green("ok") : red("fail")}: ${check.name} - ${check.detail}`,
|
|
94
|
-
);
|
|
95
|
-
});
|
|
96
|
-
if (report.checks.some((check) => !check.ok)) process.exitCode = 1;
|
|
97
|
-
}
|
package/src/commands/eval.ts
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { expandHome, loadConfig } from "../config";
|
|
4
|
-
import { excludeExistingCases, parseEvalCases } from "../eval";
|
|
5
|
-
import { emitSuccess, warn } from "../output";
|
|
6
|
-
import { parseSince } from "../stats";
|
|
7
|
-
import { confirmIfNeeded } from "./shared";
|
|
8
|
-
import type { ContextAdapter } from "../adapters";
|
|
9
|
-
import { isGlobalFlag, isGlobalFlagWithValue } from "../global-flags";
|
|
10
|
-
|
|
11
|
-
export async function runEvalPromote(
|
|
12
|
-
args: string[],
|
|
13
|
-
options: { isJson: boolean; dryRun: boolean; adapter: ContextAdapter },
|
|
14
|
-
): Promise<void> {
|
|
15
|
-
let since: string | undefined;
|
|
16
|
-
let out: string | undefined;
|
|
17
|
-
let target: string | undefined;
|
|
18
|
-
let dryRun = options.dryRun;
|
|
19
|
-
let yes = false;
|
|
20
|
-
for (let i = 0; i < args.length; i++) {
|
|
21
|
-
const arg = args[i];
|
|
22
|
-
if (arg === "--since") since = args[++i];
|
|
23
|
-
else if (arg === "--out") out = args[++i];
|
|
24
|
-
else if (arg === "--target") target = args[++i];
|
|
25
|
-
else if (arg === "--dry-run") dryRun = true;
|
|
26
|
-
else if (arg === "--yes") yes = true;
|
|
27
|
-
else if (isGlobalFlag(arg, "--json", "--allow-insecure", "--verbose")) {
|
|
28
|
-
// handled globally by main()'s isJson flag; recognized here so it isn't rejected
|
|
29
|
-
} else if (isGlobalFlagWithValue(arg)) {
|
|
30
|
-
i++; // skip flag value
|
|
31
|
-
} else if (arg?.startsWith("--")) {
|
|
32
|
-
throw new Error(`unknown eval promote option: ${arg}`);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
if (!since) {
|
|
36
|
-
throw new Error("usage: skillmux eval promote --since <window> [--out <path>] [--dry-run] [--yes] [--json]");
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (out !== undefined && target !== undefined && out !== target) {
|
|
40
|
-
throw new Error("cannot specify conflicting --out and --target");
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (target !== undefined) {
|
|
44
|
-
warn("--target is deprecated, use --out instead");
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const config = await loadConfig();
|
|
48
|
-
const stateDir = expandHome(config.state_dir);
|
|
49
|
-
const targetPath = out ?? target ?? join(stateDir, "eval-observed.json");
|
|
50
|
-
const sinceDate = parseSince(since);
|
|
51
|
-
const sinceIso = sinceDate.toISOString();
|
|
52
|
-
|
|
53
|
-
const candidates = await options.adapter.evalPromote(since);
|
|
54
|
-
|
|
55
|
-
const existing = existsSync(targetPath) ? parseEvalCases(JSON.parse(readFileSync(targetPath, "utf-8"))) : [];
|
|
56
|
-
const { cases: newCases, skipped } = excludeExistingCases(candidates, existing);
|
|
57
|
-
|
|
58
|
-
warn("promoted eval cases contain raw user queries");
|
|
59
|
-
|
|
60
|
-
if (dryRun) {
|
|
61
|
-
emitSuccess(
|
|
62
|
-
{ isJson: options.isJson },
|
|
63
|
-
{ dry_run: true, since: sinceIso, target_path: targetPath, promoted: newCases.length, skipped_existing: skipped },
|
|
64
|
-
() => console.log(`promote: would write ${newCases.length} case(s) to ${targetPath} (skipped_existing=${skipped})`),
|
|
65
|
-
);
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (
|
|
70
|
-
!(await confirmIfNeeded({
|
|
71
|
-
confirmed: yes,
|
|
72
|
-
isJson: options.isJson,
|
|
73
|
-
prompt: `promote ${newCases.length} eval case(s) to ${targetPath}?`,
|
|
74
|
-
nonInteractiveError: "skillmux eval promote requires --yes when run non-interactively",
|
|
75
|
-
}))
|
|
76
|
-
)
|
|
77
|
-
return;
|
|
78
|
-
|
|
79
|
-
if (newCases.length > 0) {
|
|
80
|
-
writeFileSync(targetPath, JSON.stringify([...existing, ...newCases], null, 2) + "\n");
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
emitSuccess(
|
|
84
|
-
{ isJson: options.isJson },
|
|
85
|
-
{ dry_run: false, since: sinceIso, target_path: targetPath, promoted: newCases.length, skipped_existing: skipped },
|
|
86
|
-
() => console.log(`promote: wrote ${newCases.length} case(s) to ${targetPath} (skipped_existing=${skipped})`),
|
|
87
|
-
);
|
|
88
|
-
}
|