@skill-map/cli 0.9.0 → 0.10.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/dist/cli.js +1380 -675
- package/dist/cli.js.map +1 -1
- package/dist/index.js +15 -7
- package/dist/index.js.map +1 -1
- package/dist/kernel/index.js +15 -7
- package/dist/kernel/index.js.map +1 -1
- package/package.json +7 -2
package/dist/cli.js
CHANGED
|
@@ -180,7 +180,7 @@ function extractLogLevelFlag(argv) {
|
|
|
180
180
|
var LOGGER_ENV_VAR = ENV_VAR;
|
|
181
181
|
|
|
182
182
|
// cli/commands/check.ts
|
|
183
|
-
import { Command, Option } from "clipanion";
|
|
183
|
+
import { Command as Command2, Option as Option2 } from "clipanion";
|
|
184
184
|
|
|
185
185
|
// kernel/i18n/registry.texts.ts
|
|
186
186
|
var REGISTRY_TEXTS = {
|
|
@@ -297,7 +297,14 @@ var SKILL_MAP_DIR = ".skill-map";
|
|
|
297
297
|
var DB_FILENAME = "skill-map.db";
|
|
298
298
|
var JOBS_DIRNAME = "jobs";
|
|
299
299
|
var PLUGINS_DIRNAME = "plugins";
|
|
300
|
+
var SETTINGS_FILENAME = "settings.json";
|
|
301
|
+
var LOCAL_SETTINGS_FILENAME = "settings.local.json";
|
|
302
|
+
var IGNORE_FILENAME = ".skill-mapignore";
|
|
300
303
|
var DEFAULT_DB_REL = `${SKILL_MAP_DIR}/${DB_FILENAME}`;
|
|
304
|
+
var GITIGNORE_ENTRIES = [
|
|
305
|
+
`${SKILL_MAP_DIR}/${LOCAL_SETTINGS_FILENAME}`,
|
|
306
|
+
`${SKILL_MAP_DIR}/${DB_FILENAME}`
|
|
307
|
+
];
|
|
301
308
|
function resolveDbPath(options) {
|
|
302
309
|
if (options.db) return resolve(options.db);
|
|
303
310
|
if (options.global) return join(options.homedir, DEFAULT_DB_REL);
|
|
@@ -315,6 +322,18 @@ function defaultProjectPluginsDir(ctx) {
|
|
|
315
322
|
function defaultUserPluginsDir(ctx) {
|
|
316
323
|
return join(ctx.homedir, SKILL_MAP_DIR, PLUGINS_DIRNAME);
|
|
317
324
|
}
|
|
325
|
+
function defaultDbPath(scopeRoot) {
|
|
326
|
+
return join(scopeRoot, SKILL_MAP_DIR, DB_FILENAME);
|
|
327
|
+
}
|
|
328
|
+
function defaultSettingsPath(scopeRoot) {
|
|
329
|
+
return join(scopeRoot, SKILL_MAP_DIR, SETTINGS_FILENAME);
|
|
330
|
+
}
|
|
331
|
+
function defaultLocalSettingsPath(scopeRoot) {
|
|
332
|
+
return join(scopeRoot, SKILL_MAP_DIR, LOCAL_SETTINGS_FILENAME);
|
|
333
|
+
}
|
|
334
|
+
function defaultIgnoreFilePath(scopeRoot) {
|
|
335
|
+
return join(scopeRoot, IGNORE_FILENAME);
|
|
336
|
+
}
|
|
318
337
|
function assertDbExists(path, stderr) {
|
|
319
338
|
if (path === ":memory:" || existsSync(path)) return true;
|
|
320
339
|
stderr.write(tx(UTIL_TEXTS.dbNotFound, { path }));
|
|
@@ -1367,8 +1386,15 @@ import { readFileSync as readFileSync2 } from "fs";
|
|
|
1367
1386
|
import { dirname as dirname2, resolve as resolve3 } from "path";
|
|
1368
1387
|
import { createRequire } from "module";
|
|
1369
1388
|
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
1389
|
+
|
|
1390
|
+
// kernel/util/ajv-interop.ts
|
|
1370
1391
|
import addFormatsModule from "ajv-formats";
|
|
1371
1392
|
var addFormats = addFormatsModule.default ?? addFormatsModule;
|
|
1393
|
+
function applyAjvFormats(ajv) {
|
|
1394
|
+
addFormats(ajv);
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
// kernel/adapters/schema-validators.ts
|
|
1372
1398
|
var SCHEMA_FILES = {
|
|
1373
1399
|
node: "schemas/node.schema.json",
|
|
1374
1400
|
link: "schemas/link.schema.json",
|
|
@@ -1407,7 +1433,7 @@ function buildSchemaValidators() {
|
|
|
1407
1433
|
allErrors: true,
|
|
1408
1434
|
allowUnionTypes: true
|
|
1409
1435
|
});
|
|
1410
|
-
|
|
1436
|
+
applyAjvFormats(ajv);
|
|
1411
1437
|
for (const rel of SUPPORTING_SCHEMAS) {
|
|
1412
1438
|
const file = resolve3(specRoot, rel);
|
|
1413
1439
|
if (!existsSyncSafe(file)) continue;
|
|
@@ -1462,7 +1488,7 @@ function buildProviderFrontmatterValidator(providers) {
|
|
|
1462
1488
|
allErrors: true,
|
|
1463
1489
|
allowUnionTypes: true
|
|
1464
1490
|
});
|
|
1465
|
-
|
|
1491
|
+
applyAjvFormats(ajv);
|
|
1466
1492
|
const baseFile = resolve3(specRoot, "schemas/frontmatter/base.schema.json");
|
|
1467
1493
|
const baseSchema = JSON.parse(readFileSync2(baseFile, "utf8"));
|
|
1468
1494
|
ajv.addSchema(baseSchema);
|
|
@@ -1683,7 +1709,6 @@ import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync }
|
|
|
1683
1709
|
import { isAbsolute, join as join3, relative as relative2, resolve as resolve4 } from "path";
|
|
1684
1710
|
import { pathToFileURL } from "url";
|
|
1685
1711
|
import { Ajv2020 as Ajv20202 } from "ajv/dist/2020.js";
|
|
1686
|
-
import addFormatsModule2 from "ajv-formats";
|
|
1687
1712
|
import semver from "semver";
|
|
1688
1713
|
|
|
1689
1714
|
// kernel/i18n/plugin-loader.texts.ts
|
|
@@ -1728,7 +1753,6 @@ var HOOK_TRIGGERS = Object.freeze([
|
|
|
1728
1753
|
]);
|
|
1729
1754
|
|
|
1730
1755
|
// kernel/adapters/plugin-loader.ts
|
|
1731
|
-
var addFormats2 = addFormatsModule2.default ?? addFormatsModule2;
|
|
1732
1756
|
var DEFAULT_PLUGIN_IMPORT_TIMEOUT_MS = 5e3;
|
|
1733
1757
|
function createPluginLoader(options) {
|
|
1734
1758
|
return new PluginLoader(options);
|
|
@@ -2243,7 +2267,7 @@ function compilePluginSchema(pluginPath, relPath) {
|
|
|
2243
2267
|
}
|
|
2244
2268
|
try {
|
|
2245
2269
|
const ajv = new Ajv20202({ strict: false, allErrors: true, allowUnionTypes: true });
|
|
2246
|
-
|
|
2270
|
+
applyAjvFormats(ajv);
|
|
2247
2271
|
const compiled = ajv.compile(raw);
|
|
2248
2272
|
return { ok: true, validate: compiled };
|
|
2249
2273
|
} catch (err) {
|
|
@@ -2628,7 +2652,7 @@ var AsyncMutex = class {
|
|
|
2628
2652
|
this.#locked = true;
|
|
2629
2653
|
return;
|
|
2630
2654
|
}
|
|
2631
|
-
await new Promise((
|
|
2655
|
+
await new Promise((resolve20) => this.#waiters.push(resolve20));
|
|
2632
2656
|
this.#locked = true;
|
|
2633
2657
|
}
|
|
2634
2658
|
unlock() {
|
|
@@ -4627,11 +4651,111 @@ function formatWarning(plugin) {
|
|
|
4627
4651
|
});
|
|
4628
4652
|
}
|
|
4629
4653
|
|
|
4654
|
+
// cli/util/sm-command.ts
|
|
4655
|
+
import { Command, Option } from "clipanion";
|
|
4656
|
+
|
|
4657
|
+
// cli/util/elapsed.ts
|
|
4658
|
+
function startElapsed() {
|
|
4659
|
+
const startNs = process.hrtime.bigint();
|
|
4660
|
+
return {
|
|
4661
|
+
ms() {
|
|
4662
|
+
const elapsedNs = Number(process.hrtime.bigint() - startNs);
|
|
4663
|
+
return Math.round(elapsedNs / 1e6);
|
|
4664
|
+
},
|
|
4665
|
+
formatted() {
|
|
4666
|
+
return formatElapsed(this.ms());
|
|
4667
|
+
}
|
|
4668
|
+
};
|
|
4669
|
+
}
|
|
4670
|
+
function formatElapsed(ms) {
|
|
4671
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
4672
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
4673
|
+
const minutes = Math.floor(ms / 6e4);
|
|
4674
|
+
const seconds = Math.round(ms % 6e4 / 1e3);
|
|
4675
|
+
return `${minutes}m ${seconds}s`;
|
|
4676
|
+
}
|
|
4677
|
+
function emitDoneStderr(stderr, elapsed, quiet = false) {
|
|
4678
|
+
if (quiet) return;
|
|
4679
|
+
stderr.write(tx(UTIL_TEXTS.doneIn, { elapsed: elapsed.formatted() }));
|
|
4680
|
+
}
|
|
4681
|
+
|
|
4682
|
+
// cli/util/sm-command.ts
|
|
4683
|
+
function isEnvSet(value) {
|
|
4684
|
+
return value !== void 0 && value !== "";
|
|
4685
|
+
}
|
|
4686
|
+
var SmCommand = class extends Command {
|
|
4687
|
+
global = Option.Boolean("-g,--global", false, {
|
|
4688
|
+
description: "Operate on ~/.skill-map/ instead of ./.skill-map/."
|
|
4689
|
+
});
|
|
4690
|
+
json = Option.Boolean("--json", false, {
|
|
4691
|
+
description: "Emit machine-readable output on stdout. Suppresses pretty printing."
|
|
4692
|
+
});
|
|
4693
|
+
quiet = Option.Boolean("-q,--quiet", false, {
|
|
4694
|
+
description: 'Suppress non-error stderr output (including "done in <\u2026>").'
|
|
4695
|
+
});
|
|
4696
|
+
noColor = Option.Boolean("--no-color", false, {
|
|
4697
|
+
description: "Disable ANSI color codes."
|
|
4698
|
+
});
|
|
4699
|
+
verbose = Option.Counter("-v,--verbose", 0, {
|
|
4700
|
+
description: "Increase log level (-v=info, -vv=debug, -vvv=trace)."
|
|
4701
|
+
});
|
|
4702
|
+
db = Option.String("--db", { required: false, description: "Override the database file location (escape hatch)." });
|
|
4703
|
+
/**
|
|
4704
|
+
* Subclasses set this to `false` to opt out of the trailing
|
|
4705
|
+
* `done in <…>` line — appropriate for interactive verbs (`db shell`),
|
|
4706
|
+
* watcher loops (`watch`), and meta verbs that report a fixed
|
|
4707
|
+
* version (`version`, `help`).
|
|
4708
|
+
*/
|
|
4709
|
+
emitElapsed = true;
|
|
4710
|
+
/**
|
|
4711
|
+
* Wall-clock timer started just before `run()`. Subclasses that need
|
|
4712
|
+
* to embed `elapsedMs` in their `--json` output read `this.elapsed.ms()`.
|
|
4713
|
+
* `null` only between `Command` construction and the first
|
|
4714
|
+
* `execute()` call.
|
|
4715
|
+
*/
|
|
4716
|
+
elapsed = null;
|
|
4717
|
+
async execute() {
|
|
4718
|
+
this.applyEnvOverrides();
|
|
4719
|
+
this.applyVerboseLogger();
|
|
4720
|
+
this.elapsed = startElapsed();
|
|
4721
|
+
try {
|
|
4722
|
+
return await this.run();
|
|
4723
|
+
} finally {
|
|
4724
|
+
if (this.emitElapsed) emitDoneStderr(this.context.stderr, this.elapsed, this.quiet);
|
|
4725
|
+
}
|
|
4726
|
+
}
|
|
4727
|
+
/**
|
|
4728
|
+
* Promote spec env vars into flag values when the flag was left at
|
|
4729
|
+
* default. CLI flag wins over env var (spec § Global flags
|
|
4730
|
+
* precedence: "CLI flag wins over env var. Env var wins over config
|
|
4731
|
+
* file.").
|
|
4732
|
+
*/
|
|
4733
|
+
applyEnvOverrides() {
|
|
4734
|
+
const env = process.env;
|
|
4735
|
+
this.noColor = this.noColor || isEnvSet(env["NO_COLOR"]);
|
|
4736
|
+
this.global = this.global || env["SKILL_MAP_SCOPE"] === "global";
|
|
4737
|
+
this.json = this.json || isEnvSet(env["SKILL_MAP_JSON"]);
|
|
4738
|
+
if (this.db === void 0 && isEnvSet(env["SKILL_MAP_DB"])) {
|
|
4739
|
+
this.db = env["SKILL_MAP_DB"];
|
|
4740
|
+
}
|
|
4741
|
+
}
|
|
4742
|
+
/**
|
|
4743
|
+
* `-v` / `-vv` / `-vvv` reconfigures the kernel logger. Skipped
|
|
4744
|
+
* when `verbose === 0` so the level configured at `entry.ts` boot
|
|
4745
|
+
* (from `--log-level` / `SKILL_MAP_LOG_LEVEL`) stays in effect.
|
|
4746
|
+
*/
|
|
4747
|
+
applyVerboseLogger() {
|
|
4748
|
+
if (this.verbose <= 0) return;
|
|
4749
|
+
const level = this.verbose >= 3 ? "trace" : this.verbose === 2 ? "debug" : "info";
|
|
4750
|
+
configureLogger(new Logger({ level, stream: process.stderr }));
|
|
4751
|
+
}
|
|
4752
|
+
};
|
|
4753
|
+
|
|
4630
4754
|
// cli/commands/check.ts
|
|
4631
4755
|
var SEVERITY_ORDER = ["error", "warn", "info"];
|
|
4632
|
-
var CheckCommand = class extends
|
|
4756
|
+
var CheckCommand = class extends SmCommand {
|
|
4633
4757
|
static paths = [["check"]];
|
|
4634
|
-
static usage =
|
|
4758
|
+
static usage = Command2.Usage({
|
|
4635
4759
|
category: "Browse",
|
|
4636
4760
|
description: "Print all current issues (reads from DB, faster than sm scan --json | jq).",
|
|
4637
4761
|
details: `
|
|
@@ -4656,27 +4780,24 @@ var CheckCommand = class extends Command {
|
|
|
4656
4780
|
["Use a non-default DB file", "$0 check --db /path/to/skill-map.db"]
|
|
4657
4781
|
]
|
|
4658
4782
|
});
|
|
4659
|
-
|
|
4660
|
-
db = Option.String("--db", { required: false });
|
|
4661
|
-
json = Option.Boolean("--json", false);
|
|
4662
|
-
node = Option.String("-n,--node", {
|
|
4783
|
+
node = Option2.String("-n,--node", {
|
|
4663
4784
|
required: false,
|
|
4664
4785
|
description: "Restrict to issues whose nodeIds include the given path. Combines with --rules and --include-prob."
|
|
4665
4786
|
});
|
|
4666
|
-
rules =
|
|
4787
|
+
rules = Option2.String("--rules", {
|
|
4667
4788
|
required: false,
|
|
4668
4789
|
description: "Comma-separated rule ids (qualified or short). Restrict the issue read; with --include-prob, also filters which prob rules surface in the advisory."
|
|
4669
4790
|
});
|
|
4670
|
-
includeProb =
|
|
4791
|
+
includeProb = Option2.Boolean("--include-prob", false, {
|
|
4671
4792
|
description: "Detect probabilistic Rules and emit a stub advisory naming them (full dispatch lands at Step 10). Default off \u2192 deterministic-only, CI-safe."
|
|
4672
4793
|
});
|
|
4673
|
-
async =
|
|
4794
|
+
async = Option2.Boolean("--async", false, {
|
|
4674
4795
|
description: "Reserved companion to --include-prob: once jobs ship, returns job ids without waiting. No effect today."
|
|
4675
4796
|
});
|
|
4676
|
-
noPlugins =
|
|
4797
|
+
noPlugins = Option2.Boolean("--no-plugins", false, {
|
|
4677
4798
|
description: "Skip drop-in plugin discovery; only kernel built-ins participate in the prob detection. Same flag shape as `sm scan`."
|
|
4678
4799
|
});
|
|
4679
|
-
async
|
|
4800
|
+
async run() {
|
|
4680
4801
|
const dbPath = resolveDbPath({ global: this.global, db: this.db, ...defaultRuntimeContext() });
|
|
4681
4802
|
if (!assertDbExists(dbPath, this.context.stderr)) return ExitCode.NotFound;
|
|
4682
4803
|
const ruleFilter = parseRulesFlag(this.rules);
|
|
@@ -4785,32 +4906,7 @@ import {
|
|
|
4785
4906
|
writeFileSync
|
|
4786
4907
|
} from "fs";
|
|
4787
4908
|
import { dirname as dirname5, join as join7 } from "path";
|
|
4788
|
-
import { Command as
|
|
4789
|
-
|
|
4790
|
-
// cli/util/elapsed.ts
|
|
4791
|
-
function startElapsed() {
|
|
4792
|
-
const startNs = process.hrtime.bigint();
|
|
4793
|
-
return {
|
|
4794
|
-
ms() {
|
|
4795
|
-
const elapsedNs = Number(process.hrtime.bigint() - startNs);
|
|
4796
|
-
return Math.round(elapsedNs / 1e6);
|
|
4797
|
-
},
|
|
4798
|
-
formatted() {
|
|
4799
|
-
return formatElapsed(this.ms());
|
|
4800
|
-
}
|
|
4801
|
-
};
|
|
4802
|
-
}
|
|
4803
|
-
function formatElapsed(ms) {
|
|
4804
|
-
if (ms < 1e3) return `${ms}ms`;
|
|
4805
|
-
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
4806
|
-
const minutes = Math.floor(ms / 6e4);
|
|
4807
|
-
const seconds = Math.round(ms % 6e4 / 1e3);
|
|
4808
|
-
return `${minutes}m ${seconds}s`;
|
|
4809
|
-
}
|
|
4810
|
-
function emitDoneStderr(stderr, elapsed, quiet = false) {
|
|
4811
|
-
if (quiet) return;
|
|
4812
|
-
stderr.write(tx(UTIL_TEXTS.doneIn, { elapsed: elapsed.formatted() }));
|
|
4813
|
-
}
|
|
4909
|
+
import { Command as Command3, Option as Option3 } from "clipanion";
|
|
4814
4910
|
|
|
4815
4911
|
// cli/util/error-reporter.ts
|
|
4816
4912
|
function formatErrorMessage(err) {
|
|
@@ -4970,9 +5066,9 @@ function formatValueHuman(v) {
|
|
|
4970
5066
|
if (Array.isArray(v) || typeof v === "object" && v !== null) return JSON.stringify(v);
|
|
4971
5067
|
return String(v);
|
|
4972
5068
|
}
|
|
4973
|
-
var ConfigListCommand = class extends
|
|
5069
|
+
var ConfigListCommand = class extends SmCommand {
|
|
4974
5070
|
static paths = [["config", "list"]];
|
|
4975
|
-
static usage =
|
|
5071
|
+
static usage = Command3.Usage({
|
|
4976
5072
|
category: "Config",
|
|
4977
5073
|
description: "Print the effective config after layered merge.",
|
|
4978
5074
|
details: `
|
|
@@ -4981,10 +5077,11 @@ var ConfigListCommand = class extends Command2 {
|
|
|
4981
5077
|
Exempt from "done in <\u2026>" per spec/cli-contract.md \xA7Elapsed time.
|
|
4982
5078
|
`
|
|
4983
5079
|
});
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
5080
|
+
strict = Option3.Boolean("--strict", false);
|
|
5081
|
+
// Read-only config inspection: spec § Elapsed time exempts the
|
|
5082
|
+
// config family from the trailing "done in" line.
|
|
5083
|
+
emitElapsed = false;
|
|
5084
|
+
async run() {
|
|
4988
5085
|
const result = tryLoadConfig(
|
|
4989
5086
|
{ scope: this.global ? "global" : "project", strict: this.strict, ...defaultRuntimeContext() },
|
|
4990
5087
|
this.context.stderr
|
|
@@ -5005,9 +5102,9 @@ var ConfigListCommand = class extends Command2 {
|
|
|
5005
5102
|
return ExitCode.Ok;
|
|
5006
5103
|
}
|
|
5007
5104
|
};
|
|
5008
|
-
var ConfigGetCommand = class extends
|
|
5105
|
+
var ConfigGetCommand = class extends SmCommand {
|
|
5009
5106
|
static paths = [["config", "get"]];
|
|
5010
|
-
static usage =
|
|
5107
|
+
static usage = Command3.Usage({
|
|
5011
5108
|
category: "Config",
|
|
5012
5109
|
description: "Read a single config value by dot-path key.",
|
|
5013
5110
|
details: `
|
|
@@ -5015,11 +5112,10 @@ var ConfigGetCommand = class extends Command2 {
|
|
|
5015
5112
|
Exempt from "done in <\u2026>".
|
|
5016
5113
|
`
|
|
5017
5114
|
});
|
|
5018
|
-
key =
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
async execute() {
|
|
5115
|
+
key = Option3.String({ required: true });
|
|
5116
|
+
strict = Option3.Boolean("--strict", false);
|
|
5117
|
+
emitElapsed = false;
|
|
5118
|
+
async run() {
|
|
5023
5119
|
const result = tryLoadConfig(
|
|
5024
5120
|
{ scope: this.global ? "global" : "project", strict: this.strict, ...defaultRuntimeContext() },
|
|
5025
5121
|
this.context.stderr
|
|
@@ -5049,9 +5145,9 @@ var ConfigGetCommand = class extends Command2 {
|
|
|
5049
5145
|
return ExitCode.Ok;
|
|
5050
5146
|
}
|
|
5051
5147
|
};
|
|
5052
|
-
var ConfigShowCommand = class extends
|
|
5148
|
+
var ConfigShowCommand = class extends SmCommand {
|
|
5053
5149
|
static paths = [["config", "show"]];
|
|
5054
|
-
static usage =
|
|
5150
|
+
static usage = Command3.Usage({
|
|
5055
5151
|
category: "Config",
|
|
5056
5152
|
description: "Show a config value with the layer that set it (--source).",
|
|
5057
5153
|
details: `
|
|
@@ -5061,17 +5157,16 @@ var ConfigShowCommand = class extends Command2 {
|
|
|
5061
5157
|
Exempt from "done in <\u2026>".
|
|
5062
5158
|
`
|
|
5063
5159
|
});
|
|
5064
|
-
key =
|
|
5065
|
-
source =
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
strict = Option2.Boolean("--strict", false);
|
|
5160
|
+
key = Option3.String({ required: true });
|
|
5161
|
+
source = Option3.Boolean("--source", false);
|
|
5162
|
+
strict = Option3.Boolean("--strict", false);
|
|
5163
|
+
emitElapsed = false;
|
|
5069
5164
|
// CLI orchestrator: each branch (load failure, forbidden segment,
|
|
5070
5165
|
// unknown key, --json + --source 2x2 dispatch) is one validation gate
|
|
5071
5166
|
// or output-format pick. Splitting per branch scatters the gate from
|
|
5072
5167
|
// the value it gates.
|
|
5073
5168
|
// eslint-disable-next-line complexity
|
|
5074
|
-
async
|
|
5169
|
+
async run() {
|
|
5075
5170
|
const result = tryLoadConfig(
|
|
5076
5171
|
{ scope: this.global ? "global" : "project", strict: this.strict, ...defaultRuntimeContext() },
|
|
5077
5172
|
this.context.stderr
|
|
@@ -5134,9 +5229,9 @@ var LAYER_RANK = {
|
|
|
5134
5229
|
"project-local": 4,
|
|
5135
5230
|
override: 5
|
|
5136
5231
|
};
|
|
5137
|
-
var ConfigSetCommand = class extends
|
|
5232
|
+
var ConfigSetCommand = class extends SmCommand {
|
|
5138
5233
|
static paths = [["config", "set"]];
|
|
5139
|
-
static usage =
|
|
5234
|
+
static usage = Command3.Usage({
|
|
5140
5235
|
category: "Config",
|
|
5141
5236
|
description: "Write a config key. Project file by default; -g writes to user.",
|
|
5142
5237
|
details: `
|
|
@@ -5147,11 +5242,9 @@ var ConfigSetCommand = class extends Command2 {
|
|
|
5147
5242
|
Schema violation \u2192 exit 2, no write performed.
|
|
5148
5243
|
`
|
|
5149
5244
|
});
|
|
5150
|
-
key =
|
|
5151
|
-
value =
|
|
5152
|
-
|
|
5153
|
-
async execute() {
|
|
5154
|
-
const elapsed = startElapsed();
|
|
5245
|
+
key = Option3.String({ required: true });
|
|
5246
|
+
value = Option3.String({ required: true });
|
|
5247
|
+
async run() {
|
|
5155
5248
|
const ctx = defaultRuntimeContext();
|
|
5156
5249
|
const target = this.global ? "user" : "project";
|
|
5157
5250
|
const path = targetSettingsPath(target, ctx.cwd, ctx.homedir);
|
|
@@ -5162,7 +5255,6 @@ var ConfigSetCommand = class extends Command2 {
|
|
|
5162
5255
|
} catch (err) {
|
|
5163
5256
|
if (err instanceof ForbiddenSegmentError) {
|
|
5164
5257
|
this.context.stderr.write(tx(CONFIG_TEXTS.forbiddenKeySegment, { segment: err.segment, key: err.key }));
|
|
5165
|
-
emitDoneStderr(this.context.stderr, elapsed);
|
|
5166
5258
|
return ExitCode.Error;
|
|
5167
5259
|
}
|
|
5168
5260
|
throw err;
|
|
@@ -5171,18 +5263,16 @@ var ConfigSetCommand = class extends Command2 {
|
|
|
5171
5263
|
const result = validators.validate("project-config", current);
|
|
5172
5264
|
if (!result.ok) {
|
|
5173
5265
|
this.context.stderr.write(tx(CONFIG_TEXTS.invalidAfterSet, { errors: result.errors }));
|
|
5174
|
-
emitDoneStderr(this.context.stderr, elapsed);
|
|
5175
5266
|
return ExitCode.Error;
|
|
5176
5267
|
}
|
|
5177
5268
|
writeJsonAtomic(path, current);
|
|
5178
5269
|
this.context.stdout.write(tx(CONFIG_TEXTS.setWritten, { key: this.key, value: formatValueHuman(value), path }));
|
|
5179
|
-
emitDoneStderr(this.context.stderr, elapsed);
|
|
5180
5270
|
return ExitCode.Ok;
|
|
5181
5271
|
}
|
|
5182
5272
|
};
|
|
5183
|
-
var ConfigResetCommand = class extends
|
|
5273
|
+
var ConfigResetCommand = class extends SmCommand {
|
|
5184
5274
|
static paths = [["config", "reset"]];
|
|
5185
|
-
static usage =
|
|
5275
|
+
static usage = Command3.Usage({
|
|
5186
5276
|
category: "Config",
|
|
5187
5277
|
description: "Remove a config key from the target file (project default; -g for user).",
|
|
5188
5278
|
details: `
|
|
@@ -5190,16 +5280,13 @@ var ConfigResetCommand = class extends Command2 {
|
|
|
5190
5280
|
Idempotent \u2014 running twice is safe; absent key prints an info note and exits 0.
|
|
5191
5281
|
`
|
|
5192
5282
|
});
|
|
5193
|
-
key =
|
|
5194
|
-
|
|
5195
|
-
async execute() {
|
|
5196
|
-
const elapsed = startElapsed();
|
|
5283
|
+
key = Option3.String({ required: true });
|
|
5284
|
+
async run() {
|
|
5197
5285
|
const ctx = defaultRuntimeContext();
|
|
5198
5286
|
const target = this.global ? "user" : "project";
|
|
5199
5287
|
const path = targetSettingsPath(target, ctx.cwd, ctx.homedir);
|
|
5200
5288
|
if (!existsSync8(path)) {
|
|
5201
5289
|
this.context.stdout.write(tx(CONFIG_TEXTS.unsetNoOverride, { path, key: this.key }));
|
|
5202
|
-
emitDoneStderr(this.context.stderr, elapsed);
|
|
5203
5290
|
return ExitCode.Ok;
|
|
5204
5291
|
}
|
|
5205
5292
|
const current = readJsonObjectOrEmpty(path);
|
|
@@ -5209,19 +5296,16 @@ var ConfigResetCommand = class extends Command2 {
|
|
|
5209
5296
|
} catch (err) {
|
|
5210
5297
|
if (err instanceof ForbiddenSegmentError) {
|
|
5211
5298
|
this.context.stderr.write(tx(CONFIG_TEXTS.forbiddenKeySegment, { segment: err.segment, key: err.key }));
|
|
5212
|
-
emitDoneStderr(this.context.stderr, elapsed);
|
|
5213
5299
|
return ExitCode.Error;
|
|
5214
5300
|
}
|
|
5215
5301
|
throw err;
|
|
5216
5302
|
}
|
|
5217
5303
|
if (!removed) {
|
|
5218
5304
|
this.context.stdout.write(tx(CONFIG_TEXTS.unsetNoOverride, { path, key: this.key }));
|
|
5219
|
-
emitDoneStderr(this.context.stderr, elapsed);
|
|
5220
5305
|
return ExitCode.Ok;
|
|
5221
5306
|
}
|
|
5222
5307
|
writeJsonAtomic(path, current);
|
|
5223
5308
|
this.context.stdout.write(tx(CONFIG_TEXTS.unsetRemoved, { key: this.key, path }));
|
|
5224
|
-
emitDoneStderr(this.context.stderr, elapsed);
|
|
5225
5309
|
return ExitCode.Ok;
|
|
5226
5310
|
}
|
|
5227
5311
|
};
|
|
@@ -5237,7 +5321,7 @@ var CONFIG_COMMANDS = [
|
|
|
5237
5321
|
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
|
|
5238
5322
|
import { dirname as dirname7, resolve as resolve11 } from "path";
|
|
5239
5323
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
5240
|
-
import { Command as
|
|
5324
|
+
import { Command as Command4, Option as Option4 } from "clipanion";
|
|
5241
5325
|
|
|
5242
5326
|
// conformance/index.ts
|
|
5243
5327
|
import { spawnSync } from "child_process";
|
|
@@ -5700,9 +5784,9 @@ function resolveBinary() {
|
|
|
5700
5784
|
}
|
|
5701
5785
|
return resolve11(here, "..", "..", "bin", "sm.js");
|
|
5702
5786
|
}
|
|
5703
|
-
var ConformanceRunCommand = class extends
|
|
5787
|
+
var ConformanceRunCommand = class extends SmCommand {
|
|
5704
5788
|
static paths = [["conformance", "run"]];
|
|
5705
|
-
static usage =
|
|
5789
|
+
static usage = Command4.Usage({
|
|
5706
5790
|
category: "Introspection",
|
|
5707
5791
|
description: "Run the conformance suite \u2014 spec-owned cases plus every built-in Provider.",
|
|
5708
5792
|
details: `
|
|
@@ -5736,14 +5820,14 @@ var ConformanceRunCommand = class extends Command3 {
|
|
|
5736
5820
|
]
|
|
5737
5821
|
]
|
|
5738
5822
|
});
|
|
5739
|
-
scope =
|
|
5823
|
+
scope = Option4.String("--scope", {
|
|
5740
5824
|
required: false,
|
|
5741
5825
|
description: "Suite selector: 'all' (default), 'spec', or 'provider:<id>'."
|
|
5742
5826
|
});
|
|
5743
5827
|
// CLI orchestrator: scope resolution + per-case run loop +
|
|
5744
5828
|
// per-result render branches + global pass/fail decision.
|
|
5745
5829
|
// eslint-disable-next-line complexity
|
|
5746
|
-
async
|
|
5830
|
+
async run() {
|
|
5747
5831
|
let scopes;
|
|
5748
5832
|
try {
|
|
5749
5833
|
scopes = selectConformanceScopes(this.scope);
|
|
@@ -5864,7 +5948,7 @@ var CONFORMANCE_COMMANDS = [ConformanceRunCommand];
|
|
|
5864
5948
|
|
|
5865
5949
|
// cli/commands/db.ts
|
|
5866
5950
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
5867
|
-
import { chmod, copyFile, mkdir, rm
|
|
5951
|
+
import { chmod, copyFile, mkdir, rm } from "fs/promises";
|
|
5868
5952
|
import { dirname as dirname8, join as join9, resolve as resolve12 } from "path";
|
|
5869
5953
|
import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
|
|
5870
5954
|
|
|
@@ -5940,13 +6024,10 @@ var DB_TEXTS = {
|
|
|
5940
6024
|
};
|
|
5941
6025
|
|
|
5942
6026
|
// cli/commands/db.ts
|
|
5943
|
-
import { Command as
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
throw new Error(`refusing to operate on non-identifier table name: ${JSON.stringify(name)}`);
|
|
5948
|
-
}
|
|
5949
|
-
}
|
|
6027
|
+
import { Command as Command5, Option as Option5 } from "clipanion";
|
|
6028
|
+
|
|
6029
|
+
// cli/util/fs.ts
|
|
6030
|
+
import { stat as stat2 } from "fs/promises";
|
|
5950
6031
|
async function pathExists(path) {
|
|
5951
6032
|
try {
|
|
5952
6033
|
await stat2(path);
|
|
@@ -5964,15 +6045,23 @@ async function statOrNull(path) {
|
|
|
5964
6045
|
throw err;
|
|
5965
6046
|
}
|
|
5966
6047
|
}
|
|
6048
|
+
|
|
6049
|
+
// cli/commands/db.ts
|
|
6050
|
+
var SAFE_SQL_IDENTIFIER_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
6051
|
+
function assertSafeIdentifier(name) {
|
|
6052
|
+
if (!SAFE_SQL_IDENTIFIER_RE.test(name)) {
|
|
6053
|
+
throw new Error(`refusing to operate on non-identifier table name: ${JSON.stringify(name)}`);
|
|
6054
|
+
}
|
|
6055
|
+
}
|
|
5967
6056
|
async function chmodOwnerOnlyBestEffort(target) {
|
|
5968
6057
|
try {
|
|
5969
6058
|
await chmod(target, 384);
|
|
5970
6059
|
} catch {
|
|
5971
6060
|
}
|
|
5972
6061
|
}
|
|
5973
|
-
var DbBackupCommand = class extends
|
|
6062
|
+
var DbBackupCommand = class extends SmCommand {
|
|
5974
6063
|
static paths = [["db", "backup"]];
|
|
5975
|
-
static usage =
|
|
6064
|
+
static usage = Command5.Usage({
|
|
5976
6065
|
category: "Database",
|
|
5977
6066
|
description: "WAL checkpoint + copy the DB file to a backup.",
|
|
5978
6067
|
details: `
|
|
@@ -5982,10 +6071,8 @@ var DbBackupCommand = class extends Command4 {
|
|
|
5982
6071
|
running sm scan afterwards refreshes scan_*.
|
|
5983
6072
|
`
|
|
5984
6073
|
});
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
out = Option4.String("--out", { required: false });
|
|
5988
|
-
async execute() {
|
|
6074
|
+
out = Option5.String("--out", { required: false });
|
|
6075
|
+
async run() {
|
|
5989
6076
|
const path = resolveDbPath({ global: this.global, db: this.db, ...defaultRuntimeContext() });
|
|
5990
6077
|
if (!assertDbExists(path, this.context.stderr)) return ExitCode.NotFound;
|
|
5991
6078
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
@@ -5997,9 +6084,9 @@ var DbBackupCommand = class extends Command4 {
|
|
|
5997
6084
|
return ExitCode.Ok;
|
|
5998
6085
|
}
|
|
5999
6086
|
};
|
|
6000
|
-
var DbRestoreCommand = class extends
|
|
6087
|
+
var DbRestoreCommand = class extends SmCommand {
|
|
6001
6088
|
static paths = [["db", "restore"]];
|
|
6002
|
-
static usage =
|
|
6089
|
+
static usage = Command5.Usage({
|
|
6003
6090
|
category: "Database",
|
|
6004
6091
|
description: "Replace the active DB file with a backup.",
|
|
6005
6092
|
details: `
|
|
@@ -6010,14 +6097,12 @@ var DbRestoreCommand = class extends Command4 {
|
|
|
6010
6097
|
Dry-run bypasses the confirmation prompt.
|
|
6011
6098
|
`
|
|
6012
6099
|
});
|
|
6013
|
-
source =
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
yes = Option4.Boolean("--yes,--force", false);
|
|
6017
|
-
dryRun = Option4.Boolean("-n,--dry-run", false, {
|
|
6100
|
+
source = Option5.String({ required: true });
|
|
6101
|
+
yes = Option5.Boolean("--yes,--force", false);
|
|
6102
|
+
dryRun = Option5.Boolean("-n,--dry-run", false, {
|
|
6018
6103
|
description: "Preview the restore without overwriting the live DB."
|
|
6019
6104
|
});
|
|
6020
|
-
async
|
|
6105
|
+
async run() {
|
|
6021
6106
|
const target = resolveDbPath({ global: this.global, db: this.db, ...defaultRuntimeContext() });
|
|
6022
6107
|
const sourcePath = resolve12(this.source);
|
|
6023
6108
|
const sourceStat = await statOrNull(sourcePath);
|
|
@@ -6059,9 +6144,9 @@ var DbRestoreCommand = class extends Command4 {
|
|
|
6059
6144
|
return ExitCode.Ok;
|
|
6060
6145
|
}
|
|
6061
6146
|
};
|
|
6062
|
-
var DbResetCommand = class extends
|
|
6147
|
+
var DbResetCommand = class extends SmCommand {
|
|
6063
6148
|
static paths = [["db", "reset"]];
|
|
6064
|
-
static usage =
|
|
6149
|
+
static usage = Command5.Usage({
|
|
6065
6150
|
category: "Database",
|
|
6066
6151
|
description: "Drop scan_* (default), optionally state_*, or delete the DB entirely.",
|
|
6067
6152
|
details: `
|
|
@@ -6075,12 +6160,10 @@ var DbResetCommand = class extends Command4 {
|
|
|
6075
6160
|
preview itself is non-destructive).
|
|
6076
6161
|
`
|
|
6077
6162
|
});
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
yes = Option4.Boolean("--yes,--force", false);
|
|
6083
|
-
dryRun = Option4.Boolean("-n,--dry-run", false, {
|
|
6163
|
+
state = Option5.Boolean("--state", false);
|
|
6164
|
+
hard = Option5.Boolean("--hard", false);
|
|
6165
|
+
yes = Option5.Boolean("--yes,--force", false);
|
|
6166
|
+
dryRun = Option5.Boolean("-n,--dry-run", false, {
|
|
6084
6167
|
description: "Preview the reset without dropping any tables or unlinking any files."
|
|
6085
6168
|
});
|
|
6086
6169
|
// CLI orchestrator: --state vs --hard flag combo + --dry-run + --yes
|
|
@@ -6088,7 +6171,7 @@ var DbResetCommand = class extends Command4 {
|
|
|
6088
6171
|
// expression of the flag semantics; splitting per branch would
|
|
6089
6172
|
// distance the validations from their guards.
|
|
6090
6173
|
// eslint-disable-next-line complexity
|
|
6091
|
-
async
|
|
6174
|
+
async run() {
|
|
6092
6175
|
if (this.state && this.hard) {
|
|
6093
6176
|
this.context.stderr.write(DB_TEXTS.resetStateAndHardMutex);
|
|
6094
6177
|
return ExitCode.Error;
|
|
@@ -6176,9 +6259,9 @@ var DbResetCommand = class extends Command4 {
|
|
|
6176
6259
|
return ExitCode.Ok;
|
|
6177
6260
|
}
|
|
6178
6261
|
};
|
|
6179
|
-
var DbShellCommand = class extends
|
|
6262
|
+
var DbShellCommand = class extends SmCommand {
|
|
6180
6263
|
static paths = [["db", "shell"]];
|
|
6181
|
-
static usage =
|
|
6264
|
+
static usage = Command5.Usage({
|
|
6182
6265
|
category: "Database",
|
|
6183
6266
|
description: "Open an interactive sqlite3 shell on the DB file.",
|
|
6184
6267
|
details: `
|
|
@@ -6187,9 +6270,11 @@ var DbShellCommand = class extends Command4 {
|
|
|
6187
6270
|
sm db dump for a read-only inspection.
|
|
6188
6271
|
`
|
|
6189
6272
|
});
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6273
|
+
// Interactive shell: the spawned `sqlite3` owns the terminal. No
|
|
6274
|
+
// `done in <…>` line — the user expects to see the shell's own
|
|
6275
|
+
// prompt + farewell, not a follow-up trailer once they exit.
|
|
6276
|
+
emitElapsed = false;
|
|
6277
|
+
async run() {
|
|
6193
6278
|
const path = resolveDbPath({ global: this.global, db: this.db, ...defaultRuntimeContext() });
|
|
6194
6279
|
if (!assertDbExists(path, this.context.stderr)) return ExitCode.NotFound;
|
|
6195
6280
|
const result = spawnSync2("sqlite3", [path], { stdio: "inherit" });
|
|
@@ -6200,22 +6285,20 @@ var DbShellCommand = class extends Command4 {
|
|
|
6200
6285
|
return result.status ?? 0;
|
|
6201
6286
|
}
|
|
6202
6287
|
};
|
|
6203
|
-
var DbDumpCommand = class extends
|
|
6288
|
+
var DbDumpCommand = class extends SmCommand {
|
|
6204
6289
|
static paths = [["db", "dump"]];
|
|
6205
|
-
static usage =
|
|
6290
|
+
static usage = Command5.Usage({
|
|
6206
6291
|
category: "Database",
|
|
6207
6292
|
description: "SQL dump to stdout.",
|
|
6208
6293
|
details: "Read-only. Use --tables <names...> to limit the dump to specific tables."
|
|
6209
6294
|
});
|
|
6210
|
-
|
|
6211
|
-
db = Option4.String("--db", { required: false });
|
|
6212
|
-
tables = Option4.Array("--tables", { required: false });
|
|
6295
|
+
tables = Option5.Array("--tables", { required: false });
|
|
6213
6296
|
// CLI orchestrator: each branch (db existence, per-table identifier
|
|
6214
6297
|
// gate, sqlite3-not-found fallback, exit-status passthrough) is a
|
|
6215
6298
|
// single dispatcher decision. Splitting per branch scatters the gate
|
|
6216
6299
|
// away from the value it gates.
|
|
6217
6300
|
// eslint-disable-next-line complexity
|
|
6218
|
-
async
|
|
6301
|
+
async run() {
|
|
6219
6302
|
const path = resolveDbPath({ global: this.global, db: this.db, ...defaultRuntimeContext() });
|
|
6220
6303
|
if (!assertDbExists(path, this.context.stderr)) return ExitCode.NotFound;
|
|
6221
6304
|
const args2 = ["-readonly", path, ".dump"];
|
|
@@ -6236,9 +6319,9 @@ var DbDumpCommand = class extends Command4 {
|
|
|
6236
6319
|
return result.status ?? 0;
|
|
6237
6320
|
}
|
|
6238
6321
|
};
|
|
6239
|
-
var DbMigrateCommand = class extends
|
|
6322
|
+
var DbMigrateCommand = class extends SmCommand {
|
|
6240
6323
|
static paths = [["db", "migrate"]];
|
|
6241
|
-
static usage =
|
|
6324
|
+
static usage = Command5.Usage({
|
|
6242
6325
|
category: "Database",
|
|
6243
6326
|
description: "Apply pending kernel + plugin migrations (default) or inspect plan.",
|
|
6244
6327
|
details: `
|
|
@@ -6259,21 +6342,19 @@ var DbMigrateCommand = class extends Command4 {
|
|
|
6259
6342
|
object outside the prefix.
|
|
6260
6343
|
`
|
|
6261
6344
|
});
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
kernelOnly = Option4.Boolean("--kernel-only", false);
|
|
6269
|
-
pluginId = Option4.String("--plugin", { required: false });
|
|
6345
|
+
dryRun = Option5.Boolean("-n,--dry-run", false);
|
|
6346
|
+
status = Option5.Boolean("--status", false);
|
|
6347
|
+
to = Option5.String("--to", { required: false });
|
|
6348
|
+
noBackup = Option5.Boolean("--no-backup", false);
|
|
6349
|
+
kernelOnly = Option5.Boolean("--kernel-only", false);
|
|
6350
|
+
pluginId = Option5.String("--plugin", { required: false });
|
|
6270
6351
|
// Multi-flag CLI orchestrator: validates flag combos, optionally
|
|
6271
6352
|
// discovers plugins, fans out into status / apply branches against
|
|
6272
6353
|
// both the kernel ledger and per-plugin ledgers. Splitting per branch
|
|
6273
6354
|
// would scatter the close-to-call-site flag handling without making
|
|
6274
6355
|
// the verb easier to follow.
|
|
6275
6356
|
// eslint-disable-next-line complexity
|
|
6276
|
-
async
|
|
6357
|
+
async run() {
|
|
6277
6358
|
if (this.kernelOnly && this.pluginId !== void 0) {
|
|
6278
6359
|
this.context.stderr.write(DB_TEXTS.migrateKernelOnlyAndPluginMutex);
|
|
6279
6360
|
return ExitCode.Error;
|
|
@@ -6455,7 +6536,7 @@ var DB_COMMANDS = [
|
|
|
6455
6536
|
];
|
|
6456
6537
|
|
|
6457
6538
|
// cli/commands/export.ts
|
|
6458
|
-
import { Command as
|
|
6539
|
+
import { Command as Command6, Option as Option6 } from "clipanion";
|
|
6459
6540
|
|
|
6460
6541
|
// kernel/scan/query.ts
|
|
6461
6542
|
var HAS_VALUES = /* @__PURE__ */ new Set(["issues"]);
|
|
@@ -6612,9 +6693,9 @@ var SUPPORTED_FORMATS = ["json", "md"];
|
|
|
6612
6693
|
var DEFERRED_FORMATS = {
|
|
6613
6694
|
mermaid: EXPORT_TEXTS.formatDeferredReasonMermaid
|
|
6614
6695
|
};
|
|
6615
|
-
var ExportCommand = class extends
|
|
6696
|
+
var ExportCommand = class extends SmCommand {
|
|
6616
6697
|
static paths = [["export"]];
|
|
6617
|
-
static usage =
|
|
6698
|
+
static usage = Command6.Usage({
|
|
6618
6699
|
category: "Browse",
|
|
6619
6700
|
description: "Filtered export. Query syntax is implementation-defined pre-1.0.",
|
|
6620
6701
|
details: `
|
|
@@ -6638,11 +6719,9 @@ var ExportCommand = class extends Command5 {
|
|
|
6638
6719
|
["Whole graph as Markdown", '$0 export "" --format md']
|
|
6639
6720
|
]
|
|
6640
6721
|
});
|
|
6641
|
-
query =
|
|
6642
|
-
format =
|
|
6643
|
-
|
|
6644
|
-
db = Option5.String("--db", { required: false });
|
|
6645
|
-
async execute() {
|
|
6722
|
+
query = Option6.String({ required: true });
|
|
6723
|
+
format = Option6.String("--format", { required: false });
|
|
6724
|
+
async run() {
|
|
6646
6725
|
const format = (this.format ?? "json").toLowerCase();
|
|
6647
6726
|
if (DEFERRED_FORMATS[format]) {
|
|
6648
6727
|
this.context.stderr.write(
|
|
@@ -6826,7 +6905,7 @@ function pickTitle2(node) {
|
|
|
6826
6905
|
}
|
|
6827
6906
|
|
|
6828
6907
|
// cli/commands/graph.ts
|
|
6829
|
-
import { Command as
|
|
6908
|
+
import { Command as Command7, Option as Option7 } from "clipanion";
|
|
6830
6909
|
|
|
6831
6910
|
// cli/i18n/graph.texts.ts
|
|
6832
6911
|
var GRAPH_TEXTS = {
|
|
@@ -6836,9 +6915,9 @@ var GRAPH_TEXTS = {
|
|
|
6836
6915
|
|
|
6837
6916
|
// cli/commands/graph.ts
|
|
6838
6917
|
var DEFAULT_FORMAT = "ascii";
|
|
6839
|
-
var GraphCommand = class extends
|
|
6918
|
+
var GraphCommand = class extends SmCommand {
|
|
6840
6919
|
static paths = [["graph"]];
|
|
6841
|
-
static usage =
|
|
6920
|
+
static usage = Command7.Usage({
|
|
6842
6921
|
category: "Browse",
|
|
6843
6922
|
description: "Render the full graph via the named formatter.",
|
|
6844
6923
|
details: `
|
|
@@ -6855,15 +6934,13 @@ var GraphCommand = class extends Command6 {
|
|
|
6855
6934
|
["Use a non-default DB file", "$0 graph --db /path/to/skill-map.db"]
|
|
6856
6935
|
]
|
|
6857
6936
|
});
|
|
6858
|
-
format =
|
|
6937
|
+
format = Option7.String("--format", DEFAULT_FORMAT, {
|
|
6859
6938
|
description: `Formatter format. Must match the \`formatId\` field of a registered formatter. Default: ${DEFAULT_FORMAT}.`
|
|
6860
6939
|
});
|
|
6861
|
-
|
|
6862
|
-
db = Option6.String("--db", { required: false });
|
|
6863
|
-
noPlugins = Option6.Boolean("--no-plugins", false, {
|
|
6940
|
+
noPlugins = Option7.Boolean("--no-plugins", false, {
|
|
6864
6941
|
description: "Skip drop-in plugin discovery. Only built-in formatters participate."
|
|
6865
6942
|
});
|
|
6866
|
-
async
|
|
6943
|
+
async run() {
|
|
6867
6944
|
const dbPath = resolveDbPath({ global: this.global, db: this.db, ...defaultRuntimeContext() });
|
|
6868
6945
|
if (!assertDbExists(dbPath, this.context.stderr)) return ExitCode.NotFound;
|
|
6869
6946
|
const pluginRuntime = this.noPlugins ? emptyPluginRuntime() : await loadPluginRuntime({ scope: this.global ? "global" : "project" });
|
|
@@ -6898,12 +6975,12 @@ var GraphCommand = class extends Command6 {
|
|
|
6898
6975
|
import { readFileSync as readFileSync10 } from "fs";
|
|
6899
6976
|
import { createRequire as createRequire4 } from "module";
|
|
6900
6977
|
import { resolve as resolve13 } from "path";
|
|
6901
|
-
import { Command as
|
|
6978
|
+
import { Command as Command8, Option as Option8 } from "clipanion";
|
|
6902
6979
|
|
|
6903
6980
|
// package.json
|
|
6904
6981
|
var package_default = {
|
|
6905
6982
|
name: "@skill-map/cli",
|
|
6906
|
-
version: "0.
|
|
6983
|
+
version: "0.10.0",
|
|
6907
6984
|
description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
|
|
6908
6985
|
license: "MIT",
|
|
6909
6986
|
type: "module",
|
|
@@ -6950,6 +7027,7 @@ var package_default = {
|
|
|
6950
7027
|
scripts: {
|
|
6951
7028
|
build: "tsup",
|
|
6952
7029
|
dev: "tsup --watch",
|
|
7030
|
+
"dev:serve": "node ../scripts/dev-serve.js",
|
|
6953
7031
|
typecheck: "tsc --noEmit",
|
|
6954
7032
|
lint: "eslint .",
|
|
6955
7033
|
"lint:fix": "eslint . --fix",
|
|
@@ -6960,17 +7038,20 @@ var package_default = {
|
|
|
6960
7038
|
clean: "rm -rf dist coverage"
|
|
6961
7039
|
},
|
|
6962
7040
|
dependencies: {
|
|
7041
|
+
"@hono/node-server": "2.0.1",
|
|
6963
7042
|
"@skill-map/spec": "*",
|
|
6964
7043
|
ajv: "8.18.0",
|
|
6965
7044
|
"ajv-formats": "3.0.1",
|
|
6966
7045
|
chokidar: "5.0.0",
|
|
6967
7046
|
clipanion: "4.0.0-rc.4",
|
|
7047
|
+
hono: "4.12.16",
|
|
6968
7048
|
ignore: "7.0.5",
|
|
6969
7049
|
"js-tiktoken": "1.0.21",
|
|
6970
7050
|
"js-yaml": "4.1.1",
|
|
6971
7051
|
kysely: "0.28.16",
|
|
6972
7052
|
semver: "7.7.4",
|
|
6973
|
-
typanion: "3.14.0"
|
|
7053
|
+
typanion: "3.14.0",
|
|
7054
|
+
ws: "8.20.0"
|
|
6974
7055
|
},
|
|
6975
7056
|
devDependencies: {
|
|
6976
7057
|
"@eslint/js": "10.0.1",
|
|
@@ -6978,6 +7059,7 @@ var package_default = {
|
|
|
6978
7059
|
"@types/js-yaml": "4.0.9",
|
|
6979
7060
|
"@types/node": "24.12.2",
|
|
6980
7061
|
"@types/semver": "7.7.1",
|
|
7062
|
+
"@types/ws": "8.18.1",
|
|
6981
7063
|
c8: "11.0.0",
|
|
6982
7064
|
eslint: "10.2.1",
|
|
6983
7065
|
"eslint-plugin-import-x": "4.16.2",
|
|
@@ -7074,9 +7156,9 @@ var HELP_TEXTS = {
|
|
|
7074
7156
|
};
|
|
7075
7157
|
|
|
7076
7158
|
// cli/commands/help.ts
|
|
7077
|
-
var HelpCommand = class extends
|
|
7159
|
+
var HelpCommand = class extends Command8 {
|
|
7078
7160
|
static paths = [["help"]];
|
|
7079
|
-
static usage =
|
|
7161
|
+
static usage = Command8.Usage({
|
|
7080
7162
|
category: "Introspection",
|
|
7081
7163
|
description: "Self-describing introspection. --format human|md|json.",
|
|
7082
7164
|
details: `
|
|
@@ -7090,8 +7172,8 @@ var HelpCommand = class extends Command7 {
|
|
|
7090
7172
|
json \u2014 structured surface dump per spec/cli-contract.md.
|
|
7091
7173
|
`
|
|
7092
7174
|
});
|
|
7093
|
-
verbParts =
|
|
7094
|
-
format =
|
|
7175
|
+
verbParts = Option8.Rest({ required: 0 });
|
|
7176
|
+
format = Option8.String("--format", "human");
|
|
7095
7177
|
async execute() {
|
|
7096
7178
|
const format = normalizeFormat(this.format);
|
|
7097
7179
|
if (!format) {
|
|
@@ -7409,8 +7491,8 @@ function renderCompactOverview(verbs) {
|
|
|
7409
7491
|
lines.push(HELP_TEXTS.compactFooter);
|
|
7410
7492
|
return lines.join("\n") + "\n";
|
|
7411
7493
|
}
|
|
7412
|
-
var RootHelpCommand = class extends
|
|
7413
|
-
static paths = [["-h"], ["--help"],
|
|
7494
|
+
var RootHelpCommand = class extends Command8 {
|
|
7495
|
+
static paths = [["-h"], ["--help"], Command8.Default];
|
|
7414
7496
|
async execute() {
|
|
7415
7497
|
const rawDefs = this.cli.definitions();
|
|
7416
7498
|
const verbs = rawDefs.filter((d) => !isBuiltin(d)).map(normalizeDefinition).sort(byPath);
|
|
@@ -7465,9 +7547,9 @@ function registeredVerbPaths(cli2) {
|
|
|
7465
7547
|
}
|
|
7466
7548
|
|
|
7467
7549
|
// cli/commands/init.ts
|
|
7468
|
-
import { mkdir as mkdir2, readFile as readFile2,
|
|
7550
|
+
import { mkdir as mkdir2, readFile as readFile2, writeFile } from "fs/promises";
|
|
7469
7551
|
import { join as join10 } from "path";
|
|
7470
|
-
import { Command as
|
|
7552
|
+
import { Command as Command9, Option as Option9 } from "clipanion";
|
|
7471
7553
|
|
|
7472
7554
|
// kernel/orchestrator.ts
|
|
7473
7555
|
import { createHash } from "crypto";
|
|
@@ -8629,22 +8711,9 @@ function createCliProgressEmitter(stderr) {
|
|
|
8629
8711
|
}
|
|
8630
8712
|
|
|
8631
8713
|
// cli/commands/init.ts
|
|
8632
|
-
var
|
|
8633
|
-
".skill-map/settings.local.json",
|
|
8634
|
-
".skill-map/skill-map.db"
|
|
8635
|
-
];
|
|
8636
|
-
async function pathExists2(path) {
|
|
8637
|
-
try {
|
|
8638
|
-
await stat3(path);
|
|
8639
|
-
return true;
|
|
8640
|
-
} catch (err) {
|
|
8641
|
-
if (err.code === "ENOENT") return false;
|
|
8642
|
-
throw err;
|
|
8643
|
-
}
|
|
8644
|
-
}
|
|
8645
|
-
var InitCommand = class extends Command8 {
|
|
8714
|
+
var InitCommand = class extends SmCommand {
|
|
8646
8715
|
static paths = [["init"]];
|
|
8647
|
-
static usage =
|
|
8716
|
+
static usage = Command9.Usage({
|
|
8648
8717
|
category: "Setup",
|
|
8649
8718
|
description: "Bootstrap the current scope: scaffold .skill-map/, provision DB, run first scan.",
|
|
8650
8719
|
details: `
|
|
@@ -8668,19 +8737,16 @@ var InitCommand = class extends Command8 {
|
|
|
8668
8737
|
["Preview what would be created", "$0 init --dry-run"]
|
|
8669
8738
|
]
|
|
8670
8739
|
});
|
|
8671
|
-
|
|
8672
|
-
description: "Initialise ~/.skill-map/ instead of ./.skill-map/."
|
|
8673
|
-
});
|
|
8674
|
-
noScan = Option8.Boolean("--no-scan", false, {
|
|
8740
|
+
noScan = Option9.Boolean("--no-scan", false, {
|
|
8675
8741
|
description: "Skip the first scan after scaffolding."
|
|
8676
8742
|
});
|
|
8677
|
-
force =
|
|
8743
|
+
force = Option9.Boolean("--force", false, {
|
|
8678
8744
|
description: "Overwrite an existing settings.json / settings.local.json / .skill-mapignore."
|
|
8679
8745
|
});
|
|
8680
|
-
strict =
|
|
8746
|
+
strict = Option9.Boolean("--strict", false, {
|
|
8681
8747
|
description: "Strict mode: fail on any layered-loader warning AND promote frontmatter warnings to errors during the first scan. Same flag as sm scan / sm config."
|
|
8682
8748
|
});
|
|
8683
|
-
dryRun =
|
|
8749
|
+
dryRun = Option9.Boolean("-n,--dry-run", false, {
|
|
8684
8750
|
description: "Preview the scope provisioning without touching the filesystem or the DB. Honours --force for the would-overwrite preview. Skips the first scan unconditionally \u2014 dry-run never persists."
|
|
8685
8751
|
});
|
|
8686
8752
|
// CLI orchestrator: paths setup + dry-run branch (delegated to
|
|
@@ -8688,18 +8754,16 @@ var InitCommand = class extends Command8 {
|
|
|
8688
8754
|
// gitignore management + DB provision + first scan delegation).
|
|
8689
8755
|
// The first-scan branch already lives in `runFirstScan`.
|
|
8690
8756
|
// eslint-disable-next-line complexity
|
|
8691
|
-
async
|
|
8692
|
-
const elapsed = startElapsed();
|
|
8757
|
+
async run() {
|
|
8693
8758
|
const ctx = defaultRuntimeContext();
|
|
8694
8759
|
const scopeRoot = this.global ? ctx.homedir : ctx.cwd;
|
|
8695
8760
|
const skillMapDir = join10(scopeRoot, SKILL_MAP_DIR);
|
|
8696
|
-
const settingsPath =
|
|
8697
|
-
const localPath =
|
|
8698
|
-
const ignorePath =
|
|
8699
|
-
const dbPath =
|
|
8700
|
-
if (await
|
|
8761
|
+
const settingsPath = defaultSettingsPath(scopeRoot);
|
|
8762
|
+
const localPath = defaultLocalSettingsPath(scopeRoot);
|
|
8763
|
+
const ignorePath = defaultIgnoreFilePath(scopeRoot);
|
|
8764
|
+
const dbPath = defaultDbPath(scopeRoot);
|
|
8765
|
+
if (await pathExists(settingsPath) && !this.force) {
|
|
8701
8766
|
this.context.stderr.write(tx(INIT_TEXTS.alreadyInitialised, { settingsPath }));
|
|
8702
|
-
emitDoneStderr(this.context.stderr, elapsed);
|
|
8703
8767
|
return ExitCode.Error;
|
|
8704
8768
|
}
|
|
8705
8769
|
if (this.dryRun) {
|
|
@@ -8714,15 +8778,14 @@ var InitCommand = class extends Command8 {
|
|
|
8714
8778
|
global: this.global,
|
|
8715
8779
|
noScan: this.noScan
|
|
8716
8780
|
});
|
|
8717
|
-
emitDoneStderr(this.context.stderr, elapsed);
|
|
8718
8781
|
return ExitCode.Ok;
|
|
8719
8782
|
}
|
|
8720
8783
|
await mkdir2(skillMapDir, { recursive: true });
|
|
8721
8784
|
await writeFile(settingsPath, JSON.stringify({ schemaVersion: 1 }, null, 2) + "\n");
|
|
8722
|
-
if (!await
|
|
8785
|
+
if (!await pathExists(localPath) || this.force) {
|
|
8723
8786
|
await writeFile(localPath, "{}\n");
|
|
8724
8787
|
}
|
|
8725
|
-
if (!await
|
|
8788
|
+
if (!await pathExists(ignorePath) || this.force) {
|
|
8726
8789
|
await writeFile(ignorePath, loadBundledIgnoreText());
|
|
8727
8790
|
}
|
|
8728
8791
|
if (!this.global) {
|
|
@@ -8740,25 +8803,20 @@ var InitCommand = class extends Command8 {
|
|
|
8740
8803
|
await withSqlite({ databasePath: dbPath, autoBackup: false }, async () => {
|
|
8741
8804
|
});
|
|
8742
8805
|
this.context.stdout.write(tx(INIT_TEXTS.initialised, { skillMapDir }));
|
|
8743
|
-
if (this.noScan)
|
|
8744
|
-
|
|
8745
|
-
return ExitCode.Ok;
|
|
8746
|
-
}
|
|
8747
|
-
const scanCode = await runFirstScan(scopeRoot, ctx.homedir, dbPath, this.strict, this.context.stdout, this.context.stderr);
|
|
8748
|
-
emitDoneStderr(this.context.stderr, elapsed);
|
|
8749
|
-
return scanCode;
|
|
8806
|
+
if (this.noScan) return ExitCode.Ok;
|
|
8807
|
+
return runFirstScan(scopeRoot, ctx.homedir, dbPath, this.strict, this.context.stdout, this.context.stderr);
|
|
8750
8808
|
}
|
|
8751
8809
|
};
|
|
8752
8810
|
async function writeDryRunPlan(stdout, opts) {
|
|
8753
8811
|
stdout.write(INIT_TEXTS.dryRunHeader);
|
|
8754
|
-
if (!await
|
|
8812
|
+
if (!await pathExists(opts.skillMapDir)) {
|
|
8755
8813
|
stdout.write(tx(INIT_TEXTS.dryRunWouldCreateDir, { path: opts.skillMapDir }));
|
|
8756
8814
|
}
|
|
8757
8815
|
stdout.write(await dryRunFileMessage(opts.settingsPath));
|
|
8758
|
-
if (!await
|
|
8816
|
+
if (!await pathExists(opts.localPath) || opts.force) {
|
|
8759
8817
|
stdout.write(await dryRunFileMessage(opts.localPath));
|
|
8760
8818
|
}
|
|
8761
|
-
if (!await
|
|
8819
|
+
if (!await pathExists(opts.ignorePath) || opts.force) {
|
|
8762
8820
|
stdout.write(await dryRunFileMessage(opts.ignorePath));
|
|
8763
8821
|
}
|
|
8764
8822
|
if (!opts.global) await writeDryRunGitignorePlan(stdout, opts.scopeRoot);
|
|
@@ -8768,7 +8826,7 @@ async function writeDryRunPlan(stdout, opts) {
|
|
|
8768
8826
|
);
|
|
8769
8827
|
}
|
|
8770
8828
|
async function dryRunFileMessage(path) {
|
|
8771
|
-
return await
|
|
8829
|
+
return await pathExists(path) ? tx(INIT_TEXTS.dryRunWouldOverwriteFile, { path }) : tx(INIT_TEXTS.dryRunWouldWriteFile, { path });
|
|
8772
8830
|
}
|
|
8773
8831
|
async function writeDryRunGitignorePlan(stdout, scopeRoot) {
|
|
8774
8832
|
const wouldAdd = await previewGitignoreEntries(scopeRoot, GITIGNORE_ENTRIES);
|
|
@@ -8848,7 +8906,7 @@ async function runFirstScan(scopeRoot, homedir2, dbPath, strict, stdout, stderr)
|
|
|
8848
8906
|
}
|
|
8849
8907
|
async function previewGitignoreEntries(scopeRoot, entries) {
|
|
8850
8908
|
const path = join10(scopeRoot, ".gitignore");
|
|
8851
|
-
const body = await
|
|
8909
|
+
const body = await pathExists(path) ? await readFile2(path, "utf8") : "";
|
|
8852
8910
|
const present = new Set(
|
|
8853
8911
|
body.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"))
|
|
8854
8912
|
);
|
|
@@ -8857,7 +8915,7 @@ async function previewGitignoreEntries(scopeRoot, entries) {
|
|
|
8857
8915
|
async function ensureGitignoreEntries(scopeRoot, entries) {
|
|
8858
8916
|
const path = join10(scopeRoot, ".gitignore");
|
|
8859
8917
|
let body = "";
|
|
8860
|
-
if (await
|
|
8918
|
+
if (await pathExists(path)) {
|
|
8861
8919
|
body = await readFile2(path, "utf8");
|
|
8862
8920
|
}
|
|
8863
8921
|
const present = new Set(
|
|
@@ -8877,7 +8935,7 @@ async function ensureGitignoreEntries(scopeRoot, entries) {
|
|
|
8877
8935
|
}
|
|
8878
8936
|
|
|
8879
8937
|
// cli/commands/history.ts
|
|
8880
|
-
import { Command as
|
|
8938
|
+
import { Command as Command10, Option as Option10 } from "clipanion";
|
|
8881
8939
|
|
|
8882
8940
|
// cli/i18n/option-validators.texts.ts
|
|
8883
8941
|
var OPTION_VALIDATORS_TEXTS = {
|
|
@@ -8964,9 +9022,9 @@ function parseStatuses(input, stderr) {
|
|
|
8964
9022
|
}
|
|
8965
9023
|
return parts;
|
|
8966
9024
|
}
|
|
8967
|
-
var HistoryCommand = class extends
|
|
9025
|
+
var HistoryCommand = class extends SmCommand {
|
|
8968
9026
|
static paths = [["history"]];
|
|
8969
|
-
static usage =
|
|
9027
|
+
static usage = Command10.Usage({
|
|
8970
9028
|
category: "History",
|
|
8971
9029
|
description: "Filter execution records. --json emits an array conforming to execution-record.schema.json.",
|
|
8972
9030
|
details: `
|
|
@@ -8986,24 +9044,19 @@ var HistoryCommand = class extends Command9 {
|
|
|
8986
9044
|
["Machine-readable, scoped to one node", "$0 history -n skills/foo.md --json"]
|
|
8987
9045
|
]
|
|
8988
9046
|
});
|
|
8989
|
-
|
|
8990
|
-
|
|
8991
|
-
|
|
8992
|
-
|
|
8993
|
-
|
|
8994
|
-
|
|
8995
|
-
until = Option9.String("--until", { required: false });
|
|
8996
|
-
limit = Option9.String("--limit", { required: false });
|
|
8997
|
-
json = Option9.Boolean("--json", false);
|
|
8998
|
-
quiet = Option9.Boolean("--quiet", false);
|
|
9047
|
+
node = Option10.String("-n", { required: false });
|
|
9048
|
+
action = Option10.String("--action", { required: false });
|
|
9049
|
+
status = Option10.String("--status", { required: false });
|
|
9050
|
+
since = Option10.String("--since", { required: false });
|
|
9051
|
+
until = Option10.String("--until", { required: false });
|
|
9052
|
+
limit = Option10.String("--limit", { required: false });
|
|
8999
9053
|
// CLI list verb: many optional filter flags (`--node`, `--action`,
|
|
9000
9054
|
// `--status`, `--since`, `--until`, `--limit`, `--json`, `--quiet`)
|
|
9001
9055
|
// each adding a guarded mutation to the filter or render path. Each
|
|
9002
9056
|
// branch is single-purpose; splitting per flag would distance the
|
|
9003
9057
|
// validations from the filter they shape.
|
|
9004
9058
|
// eslint-disable-next-line complexity
|
|
9005
|
-
async
|
|
9006
|
-
const elapsed = startElapsed();
|
|
9059
|
+
async run() {
|
|
9007
9060
|
const filter = {};
|
|
9008
9061
|
if (this.node !== void 0) filter.nodePath = this.node;
|
|
9009
9062
|
if (this.action !== void 0) filter.actionId = this.action;
|
|
@@ -9038,14 +9091,13 @@ var HistoryCommand = class extends Command9 {
|
|
|
9038
9091
|
} else {
|
|
9039
9092
|
this.context.stdout.write(renderTable(rows));
|
|
9040
9093
|
}
|
|
9041
|
-
emitDoneStderr(this.context.stderr, elapsed, this.quiet);
|
|
9042
9094
|
return ExitCode.Ok;
|
|
9043
9095
|
});
|
|
9044
9096
|
}
|
|
9045
9097
|
};
|
|
9046
|
-
var HistoryStatsCommand = class extends
|
|
9098
|
+
var HistoryStatsCommand = class extends SmCommand {
|
|
9047
9099
|
static paths = [["history", "stats"]];
|
|
9048
|
-
static usage =
|
|
9100
|
+
static usage = Command10.Usage({
|
|
9049
9101
|
category: "History",
|
|
9050
9102
|
description: "Aggregate counts, tokens, periods, top nodes, and error rates over state_executions. --json conforms to history-stats.schema.json.",
|
|
9051
9103
|
details: `
|
|
@@ -9063,20 +9115,16 @@ var HistoryStatsCommand = class extends Command9 {
|
|
|
9063
9115
|
["Top 5 nodes, JSON", "$0 history stats --top 5 --json"]
|
|
9064
9116
|
]
|
|
9065
9117
|
});
|
|
9066
|
-
|
|
9067
|
-
|
|
9068
|
-
|
|
9069
|
-
|
|
9070
|
-
period = Option9.String("--period", { required: false });
|
|
9071
|
-
top = Option9.String("--top", { required: false });
|
|
9072
|
-
json = Option9.Boolean("--json", false);
|
|
9073
|
-
quiet = Option9.Boolean("--quiet", false);
|
|
9118
|
+
since = Option10.String("--since", { required: false });
|
|
9119
|
+
until = Option10.String("--until", { required: false });
|
|
9120
|
+
period = Option10.String("--period", { required: false });
|
|
9121
|
+
top = Option10.String("--top", { required: false });
|
|
9074
9122
|
// CLI stats verb: range parsing + window flags + period flag + JSON
|
|
9075
9123
|
// branch + per-period iteration. Each branch is a single-purpose
|
|
9076
9124
|
// gate; the data work lives in `aggregateHistoryStats`.
|
|
9077
9125
|
// eslint-disable-next-line complexity
|
|
9078
|
-
async
|
|
9079
|
-
const elapsed =
|
|
9126
|
+
async run() {
|
|
9127
|
+
const elapsed = this.elapsed;
|
|
9080
9128
|
let sinceMs = null;
|
|
9081
9129
|
let untilMs = Date.now();
|
|
9082
9130
|
if (this.since !== void 0) {
|
|
@@ -9140,7 +9188,6 @@ var HistoryStatsCommand = class extends Command9 {
|
|
|
9140
9188
|
} else {
|
|
9141
9189
|
this.context.stdout.write(renderStats(stats));
|
|
9142
9190
|
}
|
|
9143
|
-
emitDoneStderr(this.context.stderr, elapsed, this.quiet);
|
|
9144
9191
|
return ExitCode.Ok;
|
|
9145
9192
|
});
|
|
9146
9193
|
}
|
|
@@ -9252,7 +9299,7 @@ function formatRow(...cols) {
|
|
|
9252
9299
|
|
|
9253
9300
|
// cli/commands/jobs.ts
|
|
9254
9301
|
import { unlink } from "fs/promises";
|
|
9255
|
-
import { Command as
|
|
9302
|
+
import { Command as Command11, Option as Option11 } from "clipanion";
|
|
9256
9303
|
|
|
9257
9304
|
// kernel/jobs/orphan-files.ts
|
|
9258
9305
|
import { readdirSync as readdirSync6, statSync as statSync4 } from "fs";
|
|
@@ -9260,8 +9307,8 @@ import { join as join11, resolve as resolve15 } from "path";
|
|
|
9260
9307
|
function findOrphanJobFiles(jobsDir, referencedPaths) {
|
|
9261
9308
|
let entries;
|
|
9262
9309
|
try {
|
|
9263
|
-
const
|
|
9264
|
-
if (!
|
|
9310
|
+
const stat3 = statSync4(jobsDir);
|
|
9311
|
+
if (!stat3.isDirectory()) {
|
|
9265
9312
|
return { orphanFilePaths: [], referencedCount: referencedPaths.size };
|
|
9266
9313
|
}
|
|
9267
9314
|
entries = readdirSync6(jobsDir);
|
|
@@ -9298,9 +9345,9 @@ var JOBS_TEXTS = {
|
|
|
9298
9345
|
};
|
|
9299
9346
|
|
|
9300
9347
|
// cli/commands/jobs.ts
|
|
9301
|
-
var JobPruneCommand = class extends
|
|
9348
|
+
var JobPruneCommand = class extends SmCommand {
|
|
9302
9349
|
static paths = [["job", "prune"]];
|
|
9303
|
-
static usage =
|
|
9350
|
+
static usage = Command11.Usage({
|
|
9304
9351
|
category: "Jobs",
|
|
9305
9352
|
description: "Retention GC for completed / failed jobs (per config policy). --orphan-files removes MD files with no DB row.",
|
|
9306
9353
|
details: `
|
|
@@ -9327,16 +9374,13 @@ var JobPruneCommand = class extends Command10 {
|
|
|
9327
9374
|
["Preview without touching the DB", "$0 job prune --dry-run --json"]
|
|
9328
9375
|
]
|
|
9329
9376
|
});
|
|
9330
|
-
orphanFiles =
|
|
9377
|
+
orphanFiles = Option11.Boolean("--orphan-files", false, {
|
|
9331
9378
|
description: "Also remove MD files in .skill-map/jobs/ that have no matching state_jobs row."
|
|
9332
9379
|
});
|
|
9333
|
-
dryRun =
|
|
9380
|
+
dryRun = Option11.Boolean("-n,--dry-run", false, {
|
|
9334
9381
|
description: "Report what would be pruned without touching the DB or filesystem."
|
|
9335
9382
|
});
|
|
9336
|
-
|
|
9337
|
-
description: "Emit a structured prune-result document on stdout."
|
|
9338
|
-
});
|
|
9339
|
-
async execute() {
|
|
9383
|
+
async run() {
|
|
9340
9384
|
const ctx = defaultRuntimeContext();
|
|
9341
9385
|
const dbPath = defaultProjectDbPath(ctx);
|
|
9342
9386
|
const jobsDir = defaultProjectJobsDir(ctx);
|
|
@@ -9450,7 +9494,7 @@ function formatPolicy(seconds) {
|
|
|
9450
9494
|
}
|
|
9451
9495
|
|
|
9452
9496
|
// cli/commands/list.ts
|
|
9453
|
-
import { Command as
|
|
9497
|
+
import { Command as Command12, Option as Option12 } from "clipanion";
|
|
9454
9498
|
|
|
9455
9499
|
// cli/i18n/list.texts.ts
|
|
9456
9500
|
var LIST_TEXTS = {
|
|
@@ -9476,9 +9520,9 @@ var SORT_BY = {
|
|
|
9476
9520
|
external_refs_count: { column: "externalRefsCount", direction: "desc" }
|
|
9477
9521
|
};
|
|
9478
9522
|
var PATH_COL_WIDTH = 50;
|
|
9479
|
-
var ListCommand = class extends
|
|
9523
|
+
var ListCommand = class extends SmCommand {
|
|
9480
9524
|
static paths = [["list"]];
|
|
9481
|
-
static usage =
|
|
9525
|
+
static usage = Command12.Usage({
|
|
9482
9526
|
category: "Browse",
|
|
9483
9527
|
description: "Tabular listing of nodes. --json emits an array conforming to node.schema.json.",
|
|
9484
9528
|
details: `
|
|
@@ -9499,14 +9543,11 @@ var ListCommand = class extends Command11 {
|
|
|
9499
9543
|
["Only nodes with issues, machine-readable", "$0 list --issue --json"]
|
|
9500
9544
|
]
|
|
9501
9545
|
});
|
|
9502
|
-
|
|
9503
|
-
|
|
9504
|
-
|
|
9505
|
-
|
|
9506
|
-
|
|
9507
|
-
limit = Option11.String("--limit", { required: false });
|
|
9508
|
-
json = Option11.Boolean("--json", false);
|
|
9509
|
-
async execute() {
|
|
9546
|
+
kind = Option12.String("--kind", { required: false });
|
|
9547
|
+
issue = Option12.Boolean("--issue", false);
|
|
9548
|
+
sortBy = Option12.String("--sort-by", { required: false });
|
|
9549
|
+
limit = Option12.String("--limit", { required: false });
|
|
9550
|
+
async run() {
|
|
9510
9551
|
let sortColumn = "path";
|
|
9511
9552
|
let sortDirection = "asc";
|
|
9512
9553
|
if (this.sortBy !== void 0) {
|
|
@@ -9607,7 +9648,7 @@ function formatRow2(path, kind, out, inCount, ext, issues, bytes) {
|
|
|
9607
9648
|
}
|
|
9608
9649
|
|
|
9609
9650
|
// cli/commands/orphans.ts
|
|
9610
|
-
import { Command as
|
|
9651
|
+
import { Command as Command13, Option as Option13 } from "clipanion";
|
|
9611
9652
|
|
|
9612
9653
|
// cli/i18n/orphans.texts.ts
|
|
9613
9654
|
var ORPHANS_TEXTS = {
|
|
@@ -9660,9 +9701,9 @@ async function findActiveOrphanIssues(adapter, predicate) {
|
|
|
9660
9701
|
function isStringArray(v) {
|
|
9661
9702
|
return Array.isArray(v) && v.every((s) => typeof s === "string");
|
|
9662
9703
|
}
|
|
9663
|
-
var OrphansCommand = class extends
|
|
9704
|
+
var OrphansCommand = class extends SmCommand {
|
|
9664
9705
|
static paths = [["orphans"]];
|
|
9665
|
-
static usage =
|
|
9706
|
+
static usage = Command13.Usage({
|
|
9666
9707
|
category: "Browse",
|
|
9667
9708
|
description: "List orphan / auto-rename issues from the last scan. --json emits an array conforming to issue.schema.json.",
|
|
9668
9709
|
details: `
|
|
@@ -9677,13 +9718,8 @@ var OrphansCommand = class extends Command12 {
|
|
|
9677
9718
|
["Just the ambiguous ones, JSON", "$0 orphans --kind ambiguous --json"]
|
|
9678
9719
|
]
|
|
9679
9720
|
});
|
|
9680
|
-
|
|
9681
|
-
|
|
9682
|
-
kind = Option12.String("--kind", { required: false });
|
|
9683
|
-
json = Option12.Boolean("--json", false);
|
|
9684
|
-
quiet = Option12.Boolean("--quiet", false);
|
|
9685
|
-
async execute() {
|
|
9686
|
-
const elapsed = startElapsed();
|
|
9721
|
+
kind = Option13.String("--kind", { required: false });
|
|
9722
|
+
async run() {
|
|
9687
9723
|
let ruleFilter = null;
|
|
9688
9724
|
if (this.kind !== void 0) {
|
|
9689
9725
|
const map = {
|
|
@@ -9714,14 +9750,13 @@ var OrphansCommand = class extends Command12 {
|
|
|
9714
9750
|
} else {
|
|
9715
9751
|
this.context.stdout.write(renderOrphans(found.map((f) => f.issue)));
|
|
9716
9752
|
}
|
|
9717
|
-
emitDoneStderr(this.context.stderr, elapsed, this.quiet);
|
|
9718
9753
|
return ExitCode.Ok;
|
|
9719
9754
|
});
|
|
9720
9755
|
}
|
|
9721
9756
|
};
|
|
9722
|
-
var OrphansReconcileCommand = class extends
|
|
9757
|
+
var OrphansReconcileCommand = class extends SmCommand {
|
|
9723
9758
|
static paths = [["orphans", "reconcile"]];
|
|
9724
|
-
static usage =
|
|
9759
|
+
static usage = Command13.Usage({
|
|
9725
9760
|
category: "Browse",
|
|
9726
9761
|
description: "Migrate state_* FKs from an orphan path to a live node, resolving the orphan issue.",
|
|
9727
9762
|
details: `
|
|
@@ -9737,14 +9772,10 @@ var OrphansReconcileCommand = class extends Command12 {
|
|
|
9737
9772
|
["Reattach orphan history", "$0 orphans reconcile skills/old.md --to skills/new.md"]
|
|
9738
9773
|
]
|
|
9739
9774
|
});
|
|
9740
|
-
|
|
9741
|
-
|
|
9742
|
-
|
|
9743
|
-
|
|
9744
|
-
dryRun = Option12.Boolean("-n,--dry-run", false);
|
|
9745
|
-
quiet = Option12.Boolean("--quiet", false);
|
|
9746
|
-
async execute() {
|
|
9747
|
-
const elapsed = startElapsed();
|
|
9775
|
+
orphanPath = Option13.String({ required: true });
|
|
9776
|
+
to = Option13.String("--to", { required: true });
|
|
9777
|
+
dryRun = Option13.Boolean("-n,--dry-run", false);
|
|
9778
|
+
async run() {
|
|
9748
9779
|
const dbPath = resolveDbPath({ global: this.global, db: this.db, ...defaultRuntimeContext() });
|
|
9749
9780
|
if (!assertDbExists(dbPath, this.context.stderr)) return ExitCode.NotFound;
|
|
9750
9781
|
return withSqlite({ databasePath: dbPath, autoBackup: false }, async (adapter) => {
|
|
@@ -9807,14 +9838,13 @@ var OrphansReconcileCommand = class extends Command12 {
|
|
|
9807
9838
|
)
|
|
9808
9839
|
);
|
|
9809
9840
|
}
|
|
9810
|
-
emitDoneStderr(this.context.stderr, elapsed, this.quiet);
|
|
9811
9841
|
return ExitCode.Ok;
|
|
9812
9842
|
});
|
|
9813
9843
|
}
|
|
9814
9844
|
};
|
|
9815
|
-
var OrphansUndoRenameCommand = class extends
|
|
9845
|
+
var OrphansUndoRenameCommand = class extends SmCommand {
|
|
9816
9846
|
static paths = [["orphans", "undo-rename"]];
|
|
9817
|
-
static usage =
|
|
9847
|
+
static usage = Command13.Usage({
|
|
9818
9848
|
category: "Browse",
|
|
9819
9849
|
description: "Reverse a medium- or ambiguous-confidence auto-rename. Migrates state_* FKs back, emits a new orphan on the prior path.",
|
|
9820
9850
|
details: `
|
|
@@ -9834,15 +9864,11 @@ var OrphansUndoRenameCommand = class extends Command12 {
|
|
|
9834
9864
|
["Undo an ambiguous, picking a candidate", "$0 orphans undo-rename skills/new.md --from skills/old-a.md"]
|
|
9835
9865
|
]
|
|
9836
9866
|
});
|
|
9837
|
-
|
|
9838
|
-
|
|
9839
|
-
|
|
9840
|
-
|
|
9841
|
-
|
|
9842
|
-
dryRun = Option12.Boolean("-n,--dry-run", false);
|
|
9843
|
-
quiet = Option12.Boolean("--quiet", false);
|
|
9844
|
-
async execute() {
|
|
9845
|
-
const elapsed = startElapsed();
|
|
9867
|
+
newPath = Option13.String({ required: true });
|
|
9868
|
+
from = Option13.String("--from", { required: false });
|
|
9869
|
+
force = Option13.Boolean("--force", false);
|
|
9870
|
+
dryRun = Option13.Boolean("-n,--dry-run", false);
|
|
9871
|
+
async run() {
|
|
9846
9872
|
const dbPath = resolveDbPath({ global: this.global, db: this.db, ...defaultRuntimeContext() });
|
|
9847
9873
|
if (!assertDbExists(dbPath, this.context.stderr)) return ExitCode.NotFound;
|
|
9848
9874
|
return withSqlite({ databasePath: dbPath, autoBackup: false }, async (adapter) => {
|
|
@@ -9914,7 +9940,6 @@ var OrphansUndoRenameCommand = class extends Command12 {
|
|
|
9914
9940
|
rows: summaryTotal(summary)
|
|
9915
9941
|
})
|
|
9916
9942
|
);
|
|
9917
|
-
emitDoneStderr(this.context.stderr, elapsed, this.quiet);
|
|
9918
9943
|
return ExitCode.Ok;
|
|
9919
9944
|
});
|
|
9920
9945
|
}
|
|
@@ -10008,7 +10033,7 @@ var ORPHANS_COMMANDS = [
|
|
|
10008
10033
|
// cli/commands/plugins.ts
|
|
10009
10034
|
import { existsSync as existsSync13 } from "fs";
|
|
10010
10035
|
import { join as join12, resolve as resolve16 } from "path";
|
|
10011
|
-
import { Command as
|
|
10036
|
+
import { Command as Command14, Option as Option14 } from "clipanion";
|
|
10012
10037
|
|
|
10013
10038
|
// cli/i18n/plugins.texts.ts
|
|
10014
10039
|
var PLUGINS_TEXTS = {
|
|
@@ -10151,17 +10176,15 @@ function builtInRows(resolveEnabled) {
|
|
|
10151
10176
|
};
|
|
10152
10177
|
});
|
|
10153
10178
|
}
|
|
10154
|
-
var PluginsListCommand = class extends
|
|
10179
|
+
var PluginsListCommand = class extends SmCommand {
|
|
10155
10180
|
static paths = [["plugins", "list"]];
|
|
10156
|
-
static usage =
|
|
10181
|
+
static usage = Command14.Usage({
|
|
10157
10182
|
category: "Plugins",
|
|
10158
10183
|
description: "List discovered plugins and their load status.",
|
|
10159
10184
|
details: "Scans <scope>/.skill-map/plugins and ~/.skill-map/plugins (or --plugin-dir <path>). Built-in bundles (claude, core) are listed alongside user plugins."
|
|
10160
10185
|
});
|
|
10161
|
-
|
|
10162
|
-
|
|
10163
|
-
json = Option13.Boolean("--json", false);
|
|
10164
|
-
async execute() {
|
|
10186
|
+
pluginDir = Option14.String("--plugin-dir", { required: false });
|
|
10187
|
+
async run() {
|
|
10165
10188
|
const plugins = await loadAll({ global: this.global, pluginDir: this.pluginDir });
|
|
10166
10189
|
const resolveEnabled = await buildResolver(this.global);
|
|
10167
10190
|
const builtIns2 = builtInRows(resolveEnabled);
|
|
@@ -10220,24 +10243,24 @@ function renderPluginRow(p) {
|
|
|
10220
10243
|
tail
|
|
10221
10244
|
}) + "\n";
|
|
10222
10245
|
}
|
|
10223
|
-
var PluginsShowCommand = class extends
|
|
10246
|
+
var PluginsShowCommand = class extends SmCommand {
|
|
10224
10247
|
static paths = [["plugins", "show"]];
|
|
10225
|
-
static usage =
|
|
10248
|
+
static usage = Command14.Usage({
|
|
10226
10249
|
category: "Plugins",
|
|
10227
10250
|
description: "Show a single plugin's manifest + loaded extensions."
|
|
10228
10251
|
});
|
|
10229
|
-
id =
|
|
10230
|
-
|
|
10231
|
-
|
|
10232
|
-
json = Option13.Boolean("--json", false);
|
|
10233
|
-
async execute() {
|
|
10252
|
+
id = Option14.String({ required: true });
|
|
10253
|
+
pluginDir = Option14.String("--plugin-dir", { required: false });
|
|
10254
|
+
async run() {
|
|
10234
10255
|
const plugins = await loadAll({ global: this.global, pluginDir: this.pluginDir });
|
|
10235
10256
|
const resolveEnabled = await buildResolver(this.global);
|
|
10236
10257
|
const builtIns2 = builtInRows(resolveEnabled);
|
|
10237
10258
|
const builtIn = builtIns2.find((b) => b.id === this.id);
|
|
10238
10259
|
const match = plugins.find((p) => p.id === this.id);
|
|
10239
10260
|
if (!builtIn && !match) {
|
|
10240
|
-
this.context.stderr.write(
|
|
10261
|
+
this.context.stderr.write(
|
|
10262
|
+
tx(PLUGINS_TEXTS.pluginNotFound, { id: sanitizeForTerminal(this.id) }) + "\n"
|
|
10263
|
+
);
|
|
10241
10264
|
return ExitCode.NotFound;
|
|
10242
10265
|
}
|
|
10243
10266
|
if (this.json) {
|
|
@@ -10423,15 +10446,14 @@ function collectExplorationDirWarnings(plugins, homedir2) {
|
|
|
10423
10446
|
});
|
|
10424
10447
|
return out;
|
|
10425
10448
|
}
|
|
10426
|
-
var PluginsDoctorCommand = class extends
|
|
10449
|
+
var PluginsDoctorCommand = class extends SmCommand {
|
|
10427
10450
|
static paths = [["plugins", "doctor"]];
|
|
10428
|
-
static usage =
|
|
10451
|
+
static usage = Command14.Usage({
|
|
10429
10452
|
category: "Plugins",
|
|
10430
10453
|
description: "Run the full load pass and summarise by failure mode.",
|
|
10431
10454
|
details: "Exit code 0 when every plugin loads or is intentionally disabled; 1 when any plugin is in an error / incompat state."
|
|
10432
10455
|
});
|
|
10433
|
-
|
|
10434
|
-
pluginDir = Option13.String("--plugin-dir", { required: false });
|
|
10456
|
+
pluginDir = Option14.String("--plugin-dir", { required: false });
|
|
10435
10457
|
// Doctor verb: counts by status + applicableKinds warnings +
|
|
10436
10458
|
// explorationDir warnings + bad-plugins issues, each with its own
|
|
10437
10459
|
// gated render. Branching is intrinsic to the multi-section diagnostic
|
|
@@ -10439,7 +10461,7 @@ var PluginsDoctorCommand = class extends Command13 {
|
|
|
10439
10461
|
// `collectApplicableKindWarnings`, `collectExplorationDirWarnings`)
|
|
10440
10462
|
// already encapsulate the data gathering.
|
|
10441
10463
|
// eslint-disable-next-line complexity
|
|
10442
|
-
async
|
|
10464
|
+
async run() {
|
|
10443
10465
|
const plugins = await loadAll({ global: this.global, pluginDir: this.pluginDir });
|
|
10444
10466
|
const resolveEnabled = await buildResolver(this.global);
|
|
10445
10467
|
const builtIns2 = builtInRows(resolveEnabled);
|
|
@@ -10548,17 +10570,25 @@ function resolveToggleTarget(id, catalogue, verb) {
|
|
|
10548
10570
|
if (id.includes("/")) {
|
|
10549
10571
|
const [bundleId, extId, ...rest] = id.split("/");
|
|
10550
10572
|
if (!bundleId || !extId || rest.length > 0) {
|
|
10551
|
-
return {
|
|
10573
|
+
return {
|
|
10574
|
+
error: tx(PLUGINS_TEXTS.qualifiedIdUnknownBundle, {
|
|
10575
|
+
bundleId: sanitizeForTerminal(id)
|
|
10576
|
+
})
|
|
10577
|
+
};
|
|
10552
10578
|
}
|
|
10553
10579
|
const bundle2 = catalogue.find((b) => b.id === bundleId);
|
|
10554
10580
|
if (!bundle2) {
|
|
10555
|
-
return {
|
|
10581
|
+
return {
|
|
10582
|
+
error: tx(PLUGINS_TEXTS.qualifiedIdUnknownBundle, {
|
|
10583
|
+
bundleId: sanitizeForTerminal(bundleId)
|
|
10584
|
+
})
|
|
10585
|
+
};
|
|
10556
10586
|
}
|
|
10557
10587
|
if (bundle2.granularity === "bundle") {
|
|
10558
10588
|
return {
|
|
10559
10589
|
error: tx(PLUGINS_TEXTS.granularityBundleRejectsQualified, {
|
|
10560
|
-
bundleId,
|
|
10561
|
-
extId,
|
|
10590
|
+
bundleId: sanitizeForTerminal(bundleId),
|
|
10591
|
+
extId: sanitizeForTerminal(extId),
|
|
10562
10592
|
verb
|
|
10563
10593
|
})
|
|
10564
10594
|
};
|
|
@@ -10566,9 +10596,9 @@ function resolveToggleTarget(id, catalogue, verb) {
|
|
|
10566
10596
|
if (!bundle2.extensionIds.includes(extId)) {
|
|
10567
10597
|
return {
|
|
10568
10598
|
error: tx(PLUGINS_TEXTS.qualifiedIdNotFound, {
|
|
10569
|
-
id,
|
|
10570
|
-
bundleId,
|
|
10571
|
-
extId
|
|
10599
|
+
id: sanitizeForTerminal(id),
|
|
10600
|
+
bundleId: sanitizeForTerminal(bundleId),
|
|
10601
|
+
extId: sanitizeForTerminal(extId)
|
|
10572
10602
|
})
|
|
10573
10603
|
};
|
|
10574
10604
|
}
|
|
@@ -10576,34 +10606,30 @@ function resolveToggleTarget(id, catalogue, verb) {
|
|
|
10576
10606
|
}
|
|
10577
10607
|
const bundle = catalogue.find((b) => b.id === id);
|
|
10578
10608
|
if (!bundle) {
|
|
10579
|
-
return { error: tx(PLUGINS_TEXTS.pluginNotFound, { id }) };
|
|
10609
|
+
return { error: tx(PLUGINS_TEXTS.pluginNotFound, { id: sanitizeForTerminal(id) }) };
|
|
10580
10610
|
}
|
|
10581
10611
|
if (bundle.granularity === "extension") {
|
|
10582
10612
|
return {
|
|
10583
10613
|
error: tx(PLUGINS_TEXTS.granularityExtensionRejectsBundleId, {
|
|
10584
|
-
bundleId: id,
|
|
10614
|
+
bundleId: sanitizeForTerminal(id),
|
|
10585
10615
|
verb
|
|
10586
10616
|
})
|
|
10587
10617
|
};
|
|
10588
10618
|
}
|
|
10589
10619
|
return { key: bundle.id };
|
|
10590
10620
|
}
|
|
10591
|
-
var TogglePluginsBase = class extends
|
|
10592
|
-
|
|
10593
|
-
|
|
10594
|
-
id = Option13.String({ required: false });
|
|
10621
|
+
var TogglePluginsBase = class extends SmCommand {
|
|
10622
|
+
all = Option14.Boolean("--all", false);
|
|
10623
|
+
id = Option14.String({ required: false });
|
|
10595
10624
|
// eslint-disable-next-line complexity
|
|
10596
10625
|
async toggle(enabled) {
|
|
10597
|
-
const elapsed = startElapsed();
|
|
10598
10626
|
const verb = enabled ? "enable" : "disable";
|
|
10599
10627
|
if (this.all && this.id) {
|
|
10600
10628
|
this.context.stderr.write(PLUGINS_TEXTS.toggleBothIdAndAll);
|
|
10601
|
-
emitDoneStderr(this.context.stderr, elapsed);
|
|
10602
10629
|
return ExitCode.Error;
|
|
10603
10630
|
}
|
|
10604
10631
|
if (!this.all && !this.id) {
|
|
10605
10632
|
this.context.stderr.write(PLUGINS_TEXTS.toggleNeitherIdNorAll);
|
|
10606
|
-
emitDoneStderr(this.context.stderr, elapsed);
|
|
10607
10633
|
return ExitCode.Error;
|
|
10608
10634
|
}
|
|
10609
10635
|
const plugins = await loadAll({
|
|
@@ -10618,7 +10644,6 @@ var TogglePluginsBase = class extends Command13 {
|
|
|
10618
10644
|
const resolved = resolveToggleTarget(this.id, catalogue, verb);
|
|
10619
10645
|
if ("error" in resolved) {
|
|
10620
10646
|
this.context.stderr.write(tx(PLUGINS_TEXTS.toggleResolveError, { error: resolved.error }));
|
|
10621
|
-
emitDoneStderr(this.context.stderr, elapsed);
|
|
10622
10647
|
return ExitCode.NotFound;
|
|
10623
10648
|
}
|
|
10624
10649
|
targets = [resolved.key];
|
|
@@ -10641,13 +10666,12 @@ var TogglePluginsBase = class extends Command13 {
|
|
|
10641
10666
|
this.context.stdout.write(tx(PLUGINS_TEXTS.toggleAppliedManyRow, { id }));
|
|
10642
10667
|
}
|
|
10643
10668
|
}
|
|
10644
|
-
emitDoneStderr(this.context.stderr, elapsed);
|
|
10645
10669
|
return ExitCode.Ok;
|
|
10646
10670
|
}
|
|
10647
10671
|
};
|
|
10648
10672
|
var PluginsEnableCommand = class extends TogglePluginsBase {
|
|
10649
10673
|
static paths = [["plugins", "enable"]];
|
|
10650
|
-
static usage =
|
|
10674
|
+
static usage = Command14.Usage({
|
|
10651
10675
|
category: "Plugins",
|
|
10652
10676
|
description: "Enable a plugin (or --all). Persists in config_plugins.",
|
|
10653
10677
|
details: `
|
|
@@ -10663,13 +10687,13 @@ var PluginsEnableCommand = class extends TogglePluginsBase {
|
|
|
10663
10687
|
with directed guidance.
|
|
10664
10688
|
`
|
|
10665
10689
|
});
|
|
10666
|
-
async
|
|
10690
|
+
async run() {
|
|
10667
10691
|
return this.toggle(true);
|
|
10668
10692
|
}
|
|
10669
10693
|
};
|
|
10670
10694
|
var PluginsDisableCommand = class extends TogglePluginsBase {
|
|
10671
10695
|
static paths = [["plugins", "disable"]];
|
|
10672
|
-
static usage =
|
|
10696
|
+
static usage = Command14.Usage({
|
|
10673
10697
|
category: "Plugins",
|
|
10674
10698
|
description: "Disable a plugin (or --all). Persists in config_plugins; does not delete files.",
|
|
10675
10699
|
details: `
|
|
@@ -10685,7 +10709,7 @@ var PluginsDisableCommand = class extends TogglePluginsBase {
|
|
|
10685
10709
|
with directed guidance.
|
|
10686
10710
|
`
|
|
10687
10711
|
});
|
|
10688
|
-
async
|
|
10712
|
+
async run() {
|
|
10689
10713
|
return this.toggle(false);
|
|
10690
10714
|
}
|
|
10691
10715
|
};
|
|
@@ -10706,7 +10730,7 @@ var PLUGIN_COMMANDS = [
|
|
|
10706
10730
|
// cli/commands/refresh.ts
|
|
10707
10731
|
import { readFile as readFile3 } from "fs/promises";
|
|
10708
10732
|
import { resolve as resolve18 } from "path";
|
|
10709
|
-
import { Command as
|
|
10733
|
+
import { Command as Command15, Option as Option15 } from "clipanion";
|
|
10710
10734
|
|
|
10711
10735
|
// cli/i18n/refresh.texts.ts
|
|
10712
10736
|
var REFRESH_TEXTS = {
|
|
@@ -10747,9 +10771,9 @@ function assertContained2(cwd, rel) {
|
|
|
10747
10771
|
}
|
|
10748
10772
|
|
|
10749
10773
|
// cli/commands/refresh.ts
|
|
10750
|
-
var RefreshCommand = class extends
|
|
10774
|
+
var RefreshCommand = class extends SmCommand {
|
|
10751
10775
|
static paths = [["refresh"]];
|
|
10752
|
-
static usage =
|
|
10776
|
+
static usage = Command15.Usage({
|
|
10753
10777
|
category: "Scan",
|
|
10754
10778
|
description: "Refresh enrichment rows: granular (single node) or batch (every stale row).",
|
|
10755
10779
|
details: `
|
|
@@ -10774,11 +10798,11 @@ var RefreshCommand = class extends Command14 {
|
|
|
10774
10798
|
["Refresh every node with stale enrichments", "$0 refresh --stale"]
|
|
10775
10799
|
]
|
|
10776
10800
|
});
|
|
10777
|
-
nodePath =
|
|
10778
|
-
stale =
|
|
10801
|
+
nodePath = Option15.String({ name: "node", required: false });
|
|
10802
|
+
stale = Option15.Boolean("--stale", false, {
|
|
10779
10803
|
description: "Refresh every node whose probabilistic enrichment row is flagged stale=1."
|
|
10780
10804
|
});
|
|
10781
|
-
noPlugins =
|
|
10805
|
+
noPlugins = Option15.Boolean("--no-plugins", false, {
|
|
10782
10806
|
description: "Skip drop-in plugin discovery; use only the built-in extractor set."
|
|
10783
10807
|
});
|
|
10784
10808
|
// The remaining cyclomatic count comes from CLI ergonomics that don't
|
|
@@ -10787,7 +10811,7 @@ var RefreshCommand = class extends Command14 {
|
|
|
10787
10811
|
// catch, plus the `if (probSkipCount > 0)` advisory. The inner work
|
|
10788
10812
|
// already lives in `#resolveTargetNodes` and `#runDetExtractorsAcrossNodes`.
|
|
10789
10813
|
// eslint-disable-next-line complexity
|
|
10790
|
-
async
|
|
10814
|
+
async run() {
|
|
10791
10815
|
if (this.stale && this.nodePath !== void 0) {
|
|
10792
10816
|
this.context.stderr.write(REFRESH_TEXTS.nodeAndStaleMutex);
|
|
10793
10817
|
return ExitCode.Error;
|
|
@@ -10963,7 +10987,7 @@ function stripFrontmatterFence(text) {
|
|
|
10963
10987
|
var REFRESH_COMMANDS = [RefreshCommand];
|
|
10964
10988
|
|
|
10965
10989
|
// cli/commands/scan.ts
|
|
10966
|
-
import { Command as
|
|
10990
|
+
import { Command as Command17, Option as Option17 } from "clipanion";
|
|
10967
10991
|
|
|
10968
10992
|
// cli/i18n/scan.texts.ts
|
|
10969
10993
|
var SCAN_TEXTS = {
|
|
@@ -11006,24 +11030,174 @@ var SCAN_TEXTS = {
|
|
|
11006
11030
|
compareDeltaIssueRemoved: "- [{{severity}}] {{ruleId}}: {{message}}"
|
|
11007
11031
|
};
|
|
11008
11032
|
|
|
11009
|
-
// cli/
|
|
11010
|
-
|
|
11011
|
-
|
|
11012
|
-
|
|
11013
|
-
|
|
11014
|
-
|
|
11015
|
-
|
|
11016
|
-
|
|
11017
|
-
|
|
11018
|
-
|
|
11033
|
+
// cli/util/scan-runner.ts
|
|
11034
|
+
async function runScanForCommand(opts) {
|
|
11035
|
+
const ctx = opts.ctx ?? defaultRuntimeContext();
|
|
11036
|
+
const dbPath = defaultProjectDbPath(ctx);
|
|
11037
|
+
const kernel = createKernel();
|
|
11038
|
+
const pluginRuntime = await preparePluginRuntime(opts);
|
|
11039
|
+
const extensions = registerExtensions(kernel, pluginRuntime, opts);
|
|
11040
|
+
let cfg;
|
|
11041
|
+
try {
|
|
11042
|
+
cfg = loadConfig({ scope: "project", strict: opts.strict, ...ctx }).effective;
|
|
11043
|
+
} catch (err) {
|
|
11044
|
+
return { kind: "config-error", message: formatErrorMessage(err) };
|
|
11045
|
+
}
|
|
11046
|
+
const ignoreFilter = buildScanIgnoreFilter(cfg, ctx.cwd);
|
|
11047
|
+
const strict = opts.strict || cfg.scan.strict === true;
|
|
11048
|
+
const loadPrior = makePriorLoader(opts.noBuiltIns, strict);
|
|
11049
|
+
const runScanWith = makeScanRunner(kernel, opts, ignoreFilter, strict, extensions);
|
|
11050
|
+
const willPersist = !opts.noBuiltIns && !opts.dryRun;
|
|
11051
|
+
return willPersist ? runPersistPath(opts, dbPath, strict, loadPrior, runScanWith) : runEphemeralPath(opts, dbPath, strict, loadPrior, runScanWith);
|
|
11052
|
+
}
|
|
11053
|
+
async function preparePluginRuntime(opts) {
|
|
11054
|
+
const pluginRuntime = opts.noPlugins ? emptyPluginRuntime() : await loadPluginRuntime({ scope: "project" });
|
|
11055
|
+
for (const warn of pluginRuntime.warnings) opts.stderr.write(`${warn}
|
|
11056
|
+
`);
|
|
11057
|
+
return pluginRuntime;
|
|
11058
|
+
}
|
|
11059
|
+
function registerExtensions(kernel, pluginRuntime, opts) {
|
|
11060
|
+
const extensions = composeScanExtensions({
|
|
11061
|
+
noBuiltIns: opts.noBuiltIns,
|
|
11062
|
+
pluginRuntime
|
|
11063
|
+
});
|
|
11064
|
+
if (!opts.noBuiltIns) {
|
|
11065
|
+
const enabledBuiltIns = filterBuiltInManifests(listBuiltIns(), pluginRuntime.resolveEnabled);
|
|
11066
|
+
for (const manifest of enabledBuiltIns) kernel.registry.register(manifest);
|
|
11067
|
+
}
|
|
11068
|
+
for (const manifest of pluginRuntime.manifests) kernel.registry.register(manifest);
|
|
11069
|
+
return extensions;
|
|
11070
|
+
}
|
|
11071
|
+
function buildScanIgnoreFilter(cfg, cwd) {
|
|
11072
|
+
const ignoreFileText = readIgnoreFileText(cwd);
|
|
11073
|
+
const ignoreFilterOpts = {};
|
|
11074
|
+
if (cfg.ignore.length > 0) ignoreFilterOpts.configIgnore = cfg.ignore;
|
|
11075
|
+
if (ignoreFileText !== void 0) ignoreFilterOpts.ignoreFileText = ignoreFileText;
|
|
11076
|
+
return buildIgnoreFilter(ignoreFilterOpts);
|
|
11077
|
+
}
|
|
11078
|
+
function makePriorLoader(noBuiltIns, strict) {
|
|
11079
|
+
return async (adapter) => {
|
|
11080
|
+
if (noBuiltIns) return null;
|
|
11081
|
+
const loaded = await adapter.scans.load();
|
|
11082
|
+
if (loaded.nodes.length === 0) return null;
|
|
11083
|
+
if (strict) {
|
|
11084
|
+
const validators = loadSchemaValidators();
|
|
11085
|
+
const result = validators.validate("scan-result", loaded);
|
|
11086
|
+
if (!result.ok) {
|
|
11087
|
+
throw new Error(tx(SCAN_TEXTS.priorSchemaValidationFailed, { errors: result.errors }));
|
|
11088
|
+
}
|
|
11089
|
+
}
|
|
11090
|
+
return loaded;
|
|
11091
|
+
};
|
|
11092
|
+
}
|
|
11093
|
+
function makeScanRunner(kernel, opts, ignoreFilter, strict, extensions) {
|
|
11094
|
+
return async (prior, priorExtractorRuns) => {
|
|
11095
|
+
if (opts.changed && prior === null) {
|
|
11096
|
+
opts.stderr.write(SCAN_TEXTS.changedNoPriorWarning);
|
|
11097
|
+
}
|
|
11098
|
+
const runOptions = {
|
|
11099
|
+
roots: opts.roots,
|
|
11100
|
+
// Hardcoded `'project'`: spec § Global flags lists `-g/--global`
|
|
11101
|
+
// as universal, but the per-verb § Scan table does not list it
|
|
11102
|
+
// and the semantics of "scan global" are undefined. The
|
|
11103
|
+
// ScanCommand surface accepts `-g` (inherited from SmCommand)
|
|
11104
|
+
// but ignores it here. Wire to `opts.scope` once spec defines
|
|
11105
|
+
// the contract.
|
|
11106
|
+
scope: "project",
|
|
11107
|
+
tokenize: !opts.noTokens,
|
|
11108
|
+
ignoreFilter,
|
|
11109
|
+
strict,
|
|
11110
|
+
emitter: createCliProgressEmitter(opts.stderr)
|
|
11111
|
+
};
|
|
11112
|
+
if (extensions) runOptions.extensions = extensions;
|
|
11113
|
+
if (prior) {
|
|
11114
|
+
runOptions.priorSnapshot = prior;
|
|
11115
|
+
runOptions.enableCache = opts.changed;
|
|
11116
|
+
}
|
|
11117
|
+
if (priorExtractorRuns) runOptions.priorExtractorRuns = priorExtractorRuns;
|
|
11118
|
+
return runScanWithRenames(kernel, runOptions);
|
|
11119
|
+
};
|
|
11120
|
+
}
|
|
11121
|
+
async function runPersistPath(opts, dbPath, strict, loadPrior, runScanWith) {
|
|
11122
|
+
let outcome;
|
|
11123
|
+
try {
|
|
11124
|
+
outcome = await withSqlite({ databasePath: dbPath }, async (adapter) => {
|
|
11125
|
+
const prior = await loadPrior(adapter);
|
|
11126
|
+
const priorExtractorRuns = opts.changed && prior ? await adapter.scans.loadExtractorRuns() : void 0;
|
|
11127
|
+
let scanned;
|
|
11128
|
+
try {
|
|
11129
|
+
scanned = await runScanWith(prior, priorExtractorRuns);
|
|
11130
|
+
} catch (err) {
|
|
11131
|
+
return { kind: "scan-error", message: formatErrorMessage(err) };
|
|
11132
|
+
}
|
|
11133
|
+
if (scanned.result.stats.nodesCount === 0 && !opts.allowEmpty) {
|
|
11134
|
+
const counts = await adapter.scans.countRows();
|
|
11135
|
+
const existing = counts.nodes + counts.links + counts.issues;
|
|
11136
|
+
if (existing > 0) return { kind: "guard", existing };
|
|
11137
|
+
}
|
|
11138
|
+
await adapter.scans.persist(scanned.result, {
|
|
11139
|
+
renameOps: scanned.renameOps,
|
|
11140
|
+
extractorRuns: scanned.extractorRuns,
|
|
11141
|
+
enrichments: scanned.enrichments
|
|
11142
|
+
});
|
|
11143
|
+
return { kind: "ok", ...scanned };
|
|
11144
|
+
});
|
|
11145
|
+
} catch (err) {
|
|
11146
|
+
return { kind: "scan-error", message: formatErrorMessage(err) };
|
|
11147
|
+
}
|
|
11148
|
+
if (outcome.kind === "scan-error") return outcome;
|
|
11149
|
+
if (outcome.kind === "guard") return { kind: "guard-trip", existing: outcome.existing };
|
|
11150
|
+
return {
|
|
11151
|
+
kind: "ok",
|
|
11152
|
+
result: outcome.result,
|
|
11153
|
+
renameOps: outcome.renameOps,
|
|
11154
|
+
persistedTo: dbPath,
|
|
11155
|
+
dbPath,
|
|
11156
|
+
strict
|
|
11157
|
+
};
|
|
11158
|
+
}
|
|
11159
|
+
async function runEphemeralPath(opts, dbPath, strict, loadPrior, runScanWith) {
|
|
11160
|
+
let prior;
|
|
11161
|
+
try {
|
|
11162
|
+
prior = opts.noBuiltIns ? null : await tryWithSqlite({ databasePath: dbPath, autoBackup: false }, loadPrior);
|
|
11163
|
+
} catch (err) {
|
|
11164
|
+
return { kind: "scan-error", message: formatErrorMessage(err) };
|
|
11165
|
+
}
|
|
11166
|
+
try {
|
|
11167
|
+
const scanned = await runScanWith(prior);
|
|
11168
|
+
return {
|
|
11169
|
+
kind: "ok",
|
|
11170
|
+
result: scanned.result,
|
|
11171
|
+
renameOps: scanned.renameOps,
|
|
11172
|
+
persistedTo: null,
|
|
11173
|
+
dbPath,
|
|
11174
|
+
strict
|
|
11175
|
+
};
|
|
11176
|
+
} catch (err) {
|
|
11177
|
+
return { kind: "scan-error", message: formatErrorMessage(err) };
|
|
11178
|
+
}
|
|
11179
|
+
}
|
|
11180
|
+
|
|
11181
|
+
// cli/commands/watch.ts
|
|
11182
|
+
import { Command as Command16, Option as Option16 } from "clipanion";
|
|
11183
|
+
|
|
11184
|
+
// cli/i18n/watch.texts.ts
|
|
11185
|
+
var WATCH_TEXTS = {
|
|
11186
|
+
configLoadFailure: "sm watch: {{message}}\n",
|
|
11187
|
+
initialScanFailed: "sm watch: initial scan failed \u2014 {{message}}\n",
|
|
11188
|
+
batchFailed: "sm watch: batch failed \u2014 {{message}}\n",
|
|
11189
|
+
scanFailed: "sm watch: scan failed \u2014 {{message}}\n",
|
|
11190
|
+
watcherError: "sm watch: watcher error \u2014 {{message}}\n",
|
|
11019
11191
|
starting: "sm watch: starting on {{rootsCount}} root(s), debounce {{debounceMs}}ms\n",
|
|
11020
11192
|
ready: "sm watch: ready. Press Ctrl+C to stop.\n",
|
|
11021
11193
|
stopped: "sm watch: stopped after {{batchCount}} batch(es).\n",
|
|
11022
11194
|
scannedSummary: "scanned {{nodes}} nodes / {{links}} links / {{issues}} issues in {{durationMs}}ms\n",
|
|
11023
|
-
priorSchemaValidationFailed: "prior scan-result loaded from DB failed schema validation: {{errors}}"
|
|
11195
|
+
priorSchemaValidationFailed: "prior scan-result loaded from DB failed schema validation: {{errors}}",
|
|
11196
|
+
breakerTripped: "sm watch: {{count}} consecutive batch failures \u2014 shutting down. Last error: {{message}}\n"
|
|
11024
11197
|
};
|
|
11025
11198
|
|
|
11026
11199
|
// cli/commands/watch.ts
|
|
11200
|
+
var DEFAULT_MAX_CONSECUTIVE_FAILURES = 5;
|
|
11027
11201
|
async function runWatchLoop(opts) {
|
|
11028
11202
|
const { context } = opts;
|
|
11029
11203
|
const runtimeCtx = defaultRuntimeContext();
|
|
@@ -11087,21 +11261,8 @@ async function runWatchLoop(opts) {
|
|
|
11087
11261
|
runOptions.enableCache = true;
|
|
11088
11262
|
}
|
|
11089
11263
|
if (priorExtractorRuns) runOptions.priorExtractorRuns = priorExtractorRuns;
|
|
11090
|
-
|
|
11091
|
-
|
|
11092
|
-
let extractorRuns;
|
|
11093
|
-
let enrichments;
|
|
11094
|
-
try {
|
|
11095
|
-
const ran = await runScanWithRenames(kernel, runOptions);
|
|
11096
|
-
result = ran.result;
|
|
11097
|
-
renameOps = ran.renameOps;
|
|
11098
|
-
extractorRuns = ran.extractorRuns;
|
|
11099
|
-
enrichments = ran.enrichments;
|
|
11100
|
-
} catch (err) {
|
|
11101
|
-
const message = formatErrorMessage(err);
|
|
11102
|
-
context.stderr.write(tx(WATCH_TEXTS.scanFailed, { message }));
|
|
11103
|
-
return;
|
|
11104
|
-
}
|
|
11264
|
+
const ran = await runScanWithRenames(kernel, runOptions);
|
|
11265
|
+
const { result, renameOps, extractorRuns, enrichments } = ran;
|
|
11105
11266
|
await withSqlite(
|
|
11106
11267
|
{ databasePath: dbPath },
|
|
11107
11268
|
(writer) => writer.scans.persist(result, { renameOps, extractorRuns, enrichments })
|
|
@@ -11135,21 +11296,38 @@ async function runWatchLoop(opts) {
|
|
|
11135
11296
|
const stopped = new Promise((r) => {
|
|
11136
11297
|
stopResolve = r;
|
|
11137
11298
|
});
|
|
11299
|
+
const breakerLimit = opts.maxConsecutiveFailures ?? DEFAULT_MAX_CONSECUTIVE_FAILURES;
|
|
11300
|
+
let consecutiveFailures = 0;
|
|
11301
|
+
let exitCode2 = ExitCode.Ok;
|
|
11302
|
+
const handleBatch = async () => {
|
|
11303
|
+
if (stopRequested) return "stop";
|
|
11304
|
+
batchCount++;
|
|
11305
|
+
try {
|
|
11306
|
+
await runOnePass();
|
|
11307
|
+
consecutiveFailures = 0;
|
|
11308
|
+
} catch (err) {
|
|
11309
|
+
const message = formatErrorMessage(err);
|
|
11310
|
+
context.stderr.write(tx(WATCH_TEXTS.batchFailed, { message }));
|
|
11311
|
+
consecutiveFailures += 1;
|
|
11312
|
+
if (breakerLimit > 0 && consecutiveFailures >= breakerLimit) {
|
|
11313
|
+
context.stderr.write(
|
|
11314
|
+
tx(WATCH_TEXTS.breakerTripped, { count: consecutiveFailures, message })
|
|
11315
|
+
);
|
|
11316
|
+
exitCode2 = ExitCode.Error;
|
|
11317
|
+
return "stop";
|
|
11318
|
+
}
|
|
11319
|
+
}
|
|
11320
|
+
if (opts.maxBatches !== void 0 && batchCount >= opts.maxBatches) return "stop";
|
|
11321
|
+
return "continue";
|
|
11322
|
+
};
|
|
11138
11323
|
const watcher = createChokidarWatcher({
|
|
11139
11324
|
roots: opts.roots,
|
|
11140
11325
|
cwd,
|
|
11141
11326
|
debounceMs,
|
|
11142
11327
|
ignoreFilter,
|
|
11143
11328
|
onBatch: async () => {
|
|
11144
|
-
|
|
11145
|
-
|
|
11146
|
-
try {
|
|
11147
|
-
await runOnePass();
|
|
11148
|
-
} catch (err) {
|
|
11149
|
-
const message = formatErrorMessage(err);
|
|
11150
|
-
context.stderr.write(tx(WATCH_TEXTS.batchFailed, { message }));
|
|
11151
|
-
}
|
|
11152
|
-
if (opts.maxBatches !== void 0 && batchCount >= opts.maxBatches) {
|
|
11329
|
+
const next = await handleBatch();
|
|
11330
|
+
if (next === "stop") {
|
|
11153
11331
|
stopRequested = true;
|
|
11154
11332
|
stopResolve?.();
|
|
11155
11333
|
}
|
|
@@ -11176,11 +11354,11 @@ async function runWatchLoop(opts) {
|
|
|
11176
11354
|
if (!opts.json) {
|
|
11177
11355
|
context.stderr.write(tx(WATCH_TEXTS.stopped, { batchCount }));
|
|
11178
11356
|
}
|
|
11179
|
-
return
|
|
11357
|
+
return exitCode2;
|
|
11180
11358
|
}
|
|
11181
|
-
var WatchCommand = class extends
|
|
11359
|
+
var WatchCommand = class extends SmCommand {
|
|
11182
11360
|
static paths = [["watch"]];
|
|
11183
|
-
static usage =
|
|
11361
|
+
static usage = Command16.Usage({
|
|
11184
11362
|
category: "Scan",
|
|
11185
11363
|
description: "Watch roots and run an incremental scan after each debounced batch of filesystem events.",
|
|
11186
11364
|
details: `
|
|
@@ -11204,36 +11382,55 @@ var WatchCommand = class extends Command15 {
|
|
|
11204
11382
|
["Stream ScanResult per batch as ndjson", "$0 watch --json"]
|
|
11205
11383
|
]
|
|
11206
11384
|
});
|
|
11207
|
-
roots =
|
|
11208
|
-
|
|
11209
|
-
description: "Emit one ScanResult document per batch as ndjson on stdout."
|
|
11210
|
-
});
|
|
11211
|
-
noTokens = Option15.Boolean("--no-tokens", false, {
|
|
11385
|
+
roots = Option16.Rest({ name: "roots" });
|
|
11386
|
+
noTokens = Option16.Boolean("--no-tokens", false, {
|
|
11212
11387
|
description: "Skip per-node token counts (cl100k_base BPE)."
|
|
11213
11388
|
});
|
|
11214
|
-
strict =
|
|
11389
|
+
strict = Option16.Boolean("--strict", false, {
|
|
11215
11390
|
description: "Promote frontmatter-validation findings from warn to error inside each batch. Does not change the watcher exit code."
|
|
11216
11391
|
});
|
|
11217
|
-
noPlugins =
|
|
11392
|
+
noPlugins = Option16.Boolean("--no-plugins", false, {
|
|
11218
11393
|
description: "Skip drop-in plugin discovery for the watcher session."
|
|
11219
11394
|
});
|
|
11220
|
-
|
|
11395
|
+
maxConsecutiveFailures = Option16.String("--max-consecutive-failures", {
|
|
11396
|
+
required: false,
|
|
11397
|
+
description: "Shut down with exit 2 after N consecutive batch failures (default 5; 0 disables the breaker)."
|
|
11398
|
+
});
|
|
11399
|
+
// Long-running verb — the watcher prints its own "stopped" line on
|
|
11400
|
+
// SIGINT / SIGTERM. Adding `done in <…>` after that would be noise.
|
|
11401
|
+
emitElapsed = false;
|
|
11402
|
+
async run() {
|
|
11221
11403
|
const roots = this.roots.length > 0 ? this.roots : ["."];
|
|
11222
|
-
|
|
11404
|
+
const breaker = parseBreakerLimit(this.maxConsecutiveFailures, this.context.stderr);
|
|
11405
|
+
if (breaker === null) return ExitCode.Error;
|
|
11406
|
+
const watchOpts = {
|
|
11223
11407
|
roots,
|
|
11224
11408
|
json: this.json,
|
|
11225
11409
|
noTokens: this.noTokens,
|
|
11226
11410
|
strict: this.strict,
|
|
11227
11411
|
noPlugins: this.noPlugins,
|
|
11228
11412
|
context: this.context
|
|
11229
|
-
}
|
|
11413
|
+
};
|
|
11414
|
+
if (breaker !== void 0) watchOpts.maxConsecutiveFailures = breaker;
|
|
11415
|
+
return runWatchLoop(watchOpts);
|
|
11230
11416
|
}
|
|
11231
11417
|
};
|
|
11418
|
+
function parseBreakerLimit(raw, stderr) {
|
|
11419
|
+
if (raw === void 0) return void 0;
|
|
11420
|
+
const trimmed = raw.trim();
|
|
11421
|
+
const parsed = Number.parseInt(trimmed, 10);
|
|
11422
|
+
if (!Number.isInteger(parsed) || parsed < 0 || String(parsed) !== trimmed) {
|
|
11423
|
+
stderr.write(`sm watch: --max-consecutive-failures must be a non-negative integer (got ${raw})
|
|
11424
|
+
`);
|
|
11425
|
+
return null;
|
|
11426
|
+
}
|
|
11427
|
+
return parsed;
|
|
11428
|
+
}
|
|
11232
11429
|
|
|
11233
11430
|
// cli/commands/scan.ts
|
|
11234
|
-
var ScanCommand = class extends
|
|
11431
|
+
var ScanCommand = class extends SmCommand {
|
|
11235
11432
|
static paths = [["scan"]];
|
|
11236
|
-
static usage =
|
|
11433
|
+
static usage = Command17.Usage({
|
|
11237
11434
|
category: "Scan",
|
|
11238
11435
|
description: "Scan roots for markdown nodes, run extractors and rules.",
|
|
11239
11436
|
details: `
|
|
@@ -11262,198 +11459,87 @@ var ScanCommand = class extends Command16 {
|
|
|
11262
11459
|
["What would the next incremental scan persist?", "$0 scan --changed -n --json"]
|
|
11263
11460
|
]
|
|
11264
11461
|
});
|
|
11265
|
-
roots =
|
|
11266
|
-
|
|
11267
|
-
description: "Emit a machine-readable ScanResult document on stdout."
|
|
11268
|
-
});
|
|
11269
|
-
noBuiltIns = Option16.Boolean("--no-built-ins", false, {
|
|
11462
|
+
roots = Option17.Rest({ name: "roots" });
|
|
11463
|
+
noBuiltIns = Option17.Boolean("--no-built-ins", false, {
|
|
11270
11464
|
description: "Skip the built-in extension set. Yields a zero-filled ScanResult (kernel-empty-boot parity); skips DB persistence."
|
|
11271
11465
|
});
|
|
11272
|
-
noPlugins =
|
|
11466
|
+
noPlugins = Option17.Boolean("--no-plugins", false, {
|
|
11273
11467
|
description: "Skip drop-in plugin discovery. Only the built-in set runs. Combine with --no-built-ins for a fully empty pipeline."
|
|
11274
11468
|
});
|
|
11275
|
-
noTokens =
|
|
11469
|
+
noTokens = Option17.Boolean("--no-tokens", false, {
|
|
11276
11470
|
description: "Skip per-node token counts (cl100k_base BPE). Leaves node.tokens undefined; spec-valid since the field is optional."
|
|
11277
11471
|
});
|
|
11278
|
-
dryRun =
|
|
11472
|
+
dryRun = Option17.Boolean("-n,--dry-run", false, {
|
|
11279
11473
|
description: "Run the scan in memory and skip every DB write. Combined with --changed, still opens the DB read-side to load the prior snapshot."
|
|
11280
11474
|
});
|
|
11281
|
-
changed =
|
|
11475
|
+
changed = Option17.Boolean("--changed", false, {
|
|
11282
11476
|
description: "Incremental scan: reuse unchanged nodes from the persisted prior snapshot. Degrades to a full scan if no prior snapshot exists."
|
|
11283
11477
|
});
|
|
11284
|
-
allowEmpty =
|
|
11478
|
+
allowEmpty = Option17.Boolean("--allow-empty", false, {
|
|
11285
11479
|
description: "Allow a zero-result scan to wipe an already-populated DB (replace-all replace by zero rows). Off by default to avoid the typo-trap where an invalid root silently clears your data."
|
|
11286
11480
|
});
|
|
11287
|
-
strict =
|
|
11481
|
+
strict = Option17.Boolean("--strict", false, {
|
|
11288
11482
|
description: "Promote frontmatter-validation findings from warn to error (exit code 1 on any violation). Overrides scan.strict from config when both are set."
|
|
11289
11483
|
});
|
|
11290
|
-
watch =
|
|
11484
|
+
watch = Option17.Boolean("--watch", false, {
|
|
11291
11485
|
description: "Long-running mode: watch the roots and trigger an incremental scan after each debounced batch of filesystem events. Alias of `sm watch`."
|
|
11292
11486
|
});
|
|
11293
|
-
|
|
11294
|
-
|
|
11295
|
-
// load + persist branch (with stranded-orphan guard) + dry-run branch
|
|
11296
|
-
// + JSON / human render with strict self-validation. The core scan
|
|
11297
|
-
// pipeline lives in `runScan` / `runScanWithRenames`; the
|
|
11298
|
-
// `loadPrior` and `runScanWith` closures below encapsulate the DB +
|
|
11299
|
-
// option wiring. Splitting per branch would scatter Clipanion's
|
|
11300
|
-
// `this.<flag>` reads from the validations they shape.
|
|
11301
|
-
// eslint-disable-next-line complexity
|
|
11302
|
-
async execute() {
|
|
11303
|
-
if (this.watch) {
|
|
11304
|
-
if (this.noBuiltIns || this.dryRun || this.changed || this.allowEmpty) {
|
|
11305
|
-
this.context.stderr.write(SCAN_TEXTS.watchCannotCombine);
|
|
11306
|
-
return ExitCode.Error;
|
|
11307
|
-
}
|
|
11308
|
-
const roots2 = this.roots.length > 0 ? this.roots : ["."];
|
|
11309
|
-
return runWatchLoop({
|
|
11310
|
-
roots: roots2,
|
|
11311
|
-
json: this.json,
|
|
11312
|
-
noTokens: this.noTokens,
|
|
11313
|
-
strict: this.strict,
|
|
11314
|
-
noPlugins: this.noPlugins,
|
|
11315
|
-
context: this.context
|
|
11316
|
-
});
|
|
11317
|
-
}
|
|
11487
|
+
async run() {
|
|
11488
|
+
if (this.watch) return this.runWatchAlias();
|
|
11318
11489
|
if (this.changed && this.noBuiltIns) {
|
|
11319
11490
|
this.context.stderr.write(SCAN_TEXTS.changedWithoutBuiltIns);
|
|
11320
11491
|
return ExitCode.Error;
|
|
11321
11492
|
}
|
|
11322
|
-
const kernel = createKernel();
|
|
11323
11493
|
const roots = this.roots.length > 0 ? this.roots : ["."];
|
|
11324
|
-
const
|
|
11325
|
-
|
|
11326
|
-
this.context.stderr.write(`${warn}
|
|
11327
|
-
`);
|
|
11328
|
-
}
|
|
11329
|
-
const extensions = composeScanExtensions({
|
|
11494
|
+
const outcome = await runScanForCommand({
|
|
11495
|
+
roots,
|
|
11330
11496
|
noBuiltIns: this.noBuiltIns,
|
|
11331
|
-
|
|
11497
|
+
noPlugins: this.noPlugins,
|
|
11498
|
+
noTokens: this.noTokens,
|
|
11499
|
+
dryRun: this.dryRun,
|
|
11500
|
+
changed: this.changed,
|
|
11501
|
+
allowEmpty: this.allowEmpty,
|
|
11502
|
+
strict: this.strict,
|
|
11503
|
+
stderr: this.context.stderr
|
|
11332
11504
|
});
|
|
11333
|
-
|
|
11334
|
-
|
|
11335
|
-
|
|
11336
|
-
|
|
11337
|
-
|
|
11338
|
-
|
|
11339
|
-
|
|
11340
|
-
|
|
11341
|
-
|
|
11342
|
-
|
|
11343
|
-
} catch (err) {
|
|
11344
|
-
const message = formatErrorMessage(err);
|
|
11345
|
-
this.context.stderr.write(tx(SCAN_TEXTS.scanFailure, { message }));
|
|
11505
|
+
return outcome.kind === "ok" ? this.renderOutcome(outcome.result, outcome.persistedTo, outcome.dbPath, outcome.strict) : this.renderFailure(outcome);
|
|
11506
|
+
}
|
|
11507
|
+
/**
|
|
11508
|
+
* `--watch` is a thin alias for the `sm watch` verb. Combining
|
|
11509
|
+
* `--watch` with one-shot-only flags is incoherent — the watcher
|
|
11510
|
+
* always persists incrementally over the prior snapshot.
|
|
11511
|
+
*/
|
|
11512
|
+
async runWatchAlias() {
|
|
11513
|
+
if (this.noBuiltIns || this.dryRun || this.changed || this.allowEmpty) {
|
|
11514
|
+
this.context.stderr.write(SCAN_TEXTS.watchCannotCombine);
|
|
11346
11515
|
return ExitCode.Error;
|
|
11347
11516
|
}
|
|
11348
|
-
|
|
11349
|
-
const
|
|
11350
|
-
|
|
11351
|
-
|
|
11352
|
-
|
|
11353
|
-
|
|
11354
|
-
|
|
11355
|
-
|
|
11356
|
-
|
|
11357
|
-
|
|
11358
|
-
|
|
11359
|
-
|
|
11360
|
-
|
|
11361
|
-
|
|
11362
|
-
|
|
11363
|
-
|
|
11364
|
-
}
|
|
11365
|
-
return loaded;
|
|
11366
|
-
};
|
|
11367
|
-
const runScanWith = async (prior, priorExtractorRuns) => {
|
|
11368
|
-
if (this.changed && prior === null) {
|
|
11369
|
-
this.context.stderr.write(SCAN_TEXTS.changedNoPriorWarning);
|
|
11370
|
-
}
|
|
11371
|
-
const runOptions = {
|
|
11372
|
-
roots,
|
|
11373
|
-
// `--global` for `sm scan` lands in Step 6 (config + onboarding).
|
|
11374
|
-
// The orchestrator already accepts the scope override; the CLI
|
|
11375
|
-
// surface defaults to `'project'` until the flag is wired.
|
|
11376
|
-
scope: "project",
|
|
11377
|
-
tokenize: !this.noTokens,
|
|
11378
|
-
ignoreFilter,
|
|
11379
|
-
strict,
|
|
11380
|
-
emitter: createCliProgressEmitter(this.context.stderr)
|
|
11381
|
-
};
|
|
11382
|
-
if (extensions) runOptions.extensions = extensions;
|
|
11383
|
-
if (prior) {
|
|
11384
|
-
runOptions.priorSnapshot = prior;
|
|
11385
|
-
runOptions.enableCache = this.changed;
|
|
11386
|
-
}
|
|
11387
|
-
if (priorExtractorRuns) runOptions.priorExtractorRuns = priorExtractorRuns;
|
|
11388
|
-
return await runScanWithRenames(kernel, runOptions);
|
|
11389
|
-
};
|
|
11390
|
-
const willPersist = !this.noBuiltIns && !this.dryRun;
|
|
11391
|
-
let result;
|
|
11392
|
-
let renameOps;
|
|
11393
|
-
let persistedTo = null;
|
|
11394
|
-
let outcome;
|
|
11395
|
-
if (willPersist) {
|
|
11396
|
-
try {
|
|
11397
|
-
outcome = await withSqlite({ databasePath: dbPath }, async (adapter) => {
|
|
11398
|
-
const prior = await loadPrior(adapter);
|
|
11399
|
-
const priorExtractorRuns = this.changed && prior ? await adapter.scans.loadExtractorRuns() : void 0;
|
|
11400
|
-
let scanned;
|
|
11401
|
-
try {
|
|
11402
|
-
scanned = await runScanWith(prior, priorExtractorRuns);
|
|
11403
|
-
} catch (err) {
|
|
11404
|
-
const message = formatErrorMessage(err);
|
|
11405
|
-
return { kind: "scan-error", message };
|
|
11406
|
-
}
|
|
11407
|
-
if (scanned.result.stats.nodesCount === 0 && !this.allowEmpty) {
|
|
11408
|
-
const counts = await adapter.scans.countRows();
|
|
11409
|
-
const existing = counts.nodes + counts.links + counts.issues;
|
|
11410
|
-
if (existing > 0) return { kind: "guard", existing };
|
|
11411
|
-
}
|
|
11412
|
-
await adapter.scans.persist(scanned.result, {
|
|
11413
|
-
renameOps: scanned.renameOps,
|
|
11414
|
-
extractorRuns: scanned.extractorRuns,
|
|
11415
|
-
enrichments: scanned.enrichments
|
|
11416
|
-
});
|
|
11417
|
-
return { kind: "ok", ...scanned };
|
|
11418
|
-
});
|
|
11419
|
-
} catch (err) {
|
|
11420
|
-
const message = formatErrorMessage(err);
|
|
11421
|
-
this.context.stderr.write(tx(SCAN_TEXTS.scanFailure, { message }));
|
|
11422
|
-
return ExitCode.Error;
|
|
11423
|
-
}
|
|
11424
|
-
if (outcome.kind === "scan-error") {
|
|
11425
|
-
this.context.stderr.write(tx(SCAN_TEXTS.scanFailure, { message: outcome.message }));
|
|
11426
|
-
return ExitCode.Error;
|
|
11427
|
-
}
|
|
11428
|
-
if (outcome.kind === "guard") {
|
|
11429
|
-
this.context.stderr.write(tx(SCAN_TEXTS.guardWipeRefused, { existing: outcome.existing }));
|
|
11430
|
-
return ExitCode.Error;
|
|
11431
|
-
}
|
|
11432
|
-
result = outcome.result;
|
|
11433
|
-
renameOps = outcome.renameOps;
|
|
11434
|
-
persistedTo = dbPath;
|
|
11435
|
-
} else {
|
|
11436
|
-
let prior;
|
|
11437
|
-
try {
|
|
11438
|
-
prior = this.noBuiltIns ? null : await tryWithSqlite(
|
|
11439
|
-
{ databasePath: dbPath, autoBackup: false },
|
|
11440
|
-
loadPrior
|
|
11441
|
-
);
|
|
11442
|
-
} catch (err) {
|
|
11443
|
-
const message = formatErrorMessage(err);
|
|
11444
|
-
this.context.stderr.write(tx(SCAN_TEXTS.scanFailure, { message }));
|
|
11445
|
-
return ExitCode.Error;
|
|
11446
|
-
}
|
|
11447
|
-
try {
|
|
11448
|
-
const scanned = await runScanWith(prior);
|
|
11449
|
-
result = scanned.result;
|
|
11450
|
-
renameOps = scanned.renameOps;
|
|
11451
|
-
} catch (err) {
|
|
11452
|
-
const message = formatErrorMessage(err);
|
|
11453
|
-
this.context.stderr.write(tx(SCAN_TEXTS.scanFailure, { message }));
|
|
11454
|
-
return ExitCode.Error;
|
|
11455
|
-
}
|
|
11517
|
+
this.emitElapsed = false;
|
|
11518
|
+
const roots = this.roots.length > 0 ? this.roots : ["."];
|
|
11519
|
+
return runWatchLoop({
|
|
11520
|
+
roots,
|
|
11521
|
+
json: this.json,
|
|
11522
|
+
noTokens: this.noTokens,
|
|
11523
|
+
strict: this.strict,
|
|
11524
|
+
noPlugins: this.noPlugins,
|
|
11525
|
+
context: this.context
|
|
11526
|
+
});
|
|
11527
|
+
}
|
|
11528
|
+
/** Render the failure branch of `IScanRunResult` to stderr. */
|
|
11529
|
+
renderFailure(outcome) {
|
|
11530
|
+
if (outcome.kind === "guard-trip") {
|
|
11531
|
+
this.context.stderr.write(tx(SCAN_TEXTS.guardWipeRefused, { existing: outcome.existing }));
|
|
11532
|
+
return ExitCode.Error;
|
|
11456
11533
|
}
|
|
11534
|
+
this.context.stderr.write(tx(SCAN_TEXTS.scanFailure, { message: outcome.message }));
|
|
11535
|
+
return ExitCode.Error;
|
|
11536
|
+
}
|
|
11537
|
+
/**
|
|
11538
|
+
* Render the successful outcome to stdout (JSON or human) and compute
|
|
11539
|
+
* the exit code. Exit 1 only when at least one issue is at `error`
|
|
11540
|
+
* severity (mirrors `sm check`, per spec § Exit codes).
|
|
11541
|
+
*/
|
|
11542
|
+
renderOutcome(result, persistedTo, dbPath, strict) {
|
|
11457
11543
|
const exitCode2 = result.issues.some((i) => i.severity === "error") ? ExitCode.Issues : ExitCode.Ok;
|
|
11458
11544
|
if (this.json) {
|
|
11459
11545
|
if (strict) {
|
|
@@ -11494,10 +11580,10 @@ var ScanCommand = class extends Command16 {
|
|
|
11494
11580
|
|
|
11495
11581
|
// cli/commands/scan-compare.ts
|
|
11496
11582
|
import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
|
|
11497
|
-
import { Command as
|
|
11498
|
-
var ScanCompareCommand = class extends
|
|
11583
|
+
import { Command as Command18, Option as Option18 } from "clipanion";
|
|
11584
|
+
var ScanCompareCommand = class extends SmCommand {
|
|
11499
11585
|
static paths = [["scan", "compare-with"]];
|
|
11500
|
-
static usage =
|
|
11586
|
+
static usage = Command18.Usage({
|
|
11501
11587
|
category: "Scan",
|
|
11502
11588
|
description: "Run a fresh scan in memory and emit a delta against the saved ScanResult dump at <dump>. Read-only.",
|
|
11503
11589
|
details: `
|
|
@@ -11525,18 +11611,15 @@ var ScanCompareCommand = class extends Command17 {
|
|
|
11525
11611
|
["JSON output for tooling", "$0 scan compare-with baseline.json --json"]
|
|
11526
11612
|
]
|
|
11527
11613
|
});
|
|
11528
|
-
dump =
|
|
11529
|
-
roots =
|
|
11530
|
-
|
|
11531
|
-
description: "Emit the IScanDelta document as JSON on stdout."
|
|
11532
|
-
});
|
|
11533
|
-
noTokens = Option17.Boolean("--no-tokens", false, {
|
|
11614
|
+
dump = Option18.String({ required: true });
|
|
11615
|
+
roots = Option18.Rest({ name: "roots" });
|
|
11616
|
+
noTokens = Option18.Boolean("--no-tokens", false, {
|
|
11534
11617
|
description: "Skip per-node token counts during the fresh scan."
|
|
11535
11618
|
});
|
|
11536
|
-
strict =
|
|
11619
|
+
strict = Option18.Boolean("--strict", false, {
|
|
11537
11620
|
description: "Promote layered-config warnings and frontmatter-validation findings from warn to error."
|
|
11538
11621
|
});
|
|
11539
|
-
noPlugins =
|
|
11622
|
+
noPlugins = Option18.Boolean("--no-plugins", false, {
|
|
11540
11623
|
description: "Skip drop-in plugin discovery."
|
|
11541
11624
|
});
|
|
11542
11625
|
// Cyclomatic count comes from CLI ergonomics: 3 distinct try/catch
|
|
@@ -11544,7 +11627,7 @@ var ScanCompareCommand = class extends Command17 {
|
|
|
11544
11627
|
// for the JSON branch. The pure pieces already live in
|
|
11545
11628
|
// `loadAndValidateDump` and `computeScanDelta`.
|
|
11546
11629
|
// eslint-disable-next-line complexity
|
|
11547
|
-
async
|
|
11630
|
+
async run() {
|
|
11548
11631
|
const ctx = defaultRuntimeContext();
|
|
11549
11632
|
const roots = this.roots.length > 0 ? this.roots : ["."];
|
|
11550
11633
|
let prior;
|
|
@@ -11719,8 +11802,646 @@ function renderDeltaIssues(issues) {
|
|
|
11719
11802
|
return lines;
|
|
11720
11803
|
}
|
|
11721
11804
|
|
|
11805
|
+
// cli/commands/serve.ts
|
|
11806
|
+
import { spawn } from "child_process";
|
|
11807
|
+
import { existsSync as existsSync18 } from "fs";
|
|
11808
|
+
import { Command as Command19, Option as Option19 } from "clipanion";
|
|
11809
|
+
|
|
11810
|
+
// server/index.ts
|
|
11811
|
+
import { serve } from "@hono/node-server";
|
|
11812
|
+
import { WebSocketServer } from "ws";
|
|
11813
|
+
|
|
11814
|
+
// server/app.ts
|
|
11815
|
+
import { Hono } from "hono";
|
|
11816
|
+
import { HTTPException } from "hono/http-exception";
|
|
11817
|
+
|
|
11818
|
+
// server/health.ts
|
|
11819
|
+
import { existsSync as existsSync15 } from "fs";
|
|
11820
|
+
var FALLBACK_SCHEMA_VERSION = "1";
|
|
11821
|
+
function buildHealth(deps) {
|
|
11822
|
+
return {
|
|
11823
|
+
ok: true,
|
|
11824
|
+
schemaVersion: FALLBACK_SCHEMA_VERSION,
|
|
11825
|
+
specVersion: deps.specVersion,
|
|
11826
|
+
implVersion: VERSION,
|
|
11827
|
+
scope: deps.scope,
|
|
11828
|
+
db: existsSync15(deps.dbPath) ? "present" : "missing"
|
|
11829
|
+
};
|
|
11830
|
+
}
|
|
11831
|
+
async function resolveSpecVersion2() {
|
|
11832
|
+
try {
|
|
11833
|
+
const mod = await import("@skill-map/spec", { with: { type: "json" } });
|
|
11834
|
+
const version = mod.default?.specPackageVersion;
|
|
11835
|
+
return version ?? "unknown";
|
|
11836
|
+
} catch {
|
|
11837
|
+
return "unknown";
|
|
11838
|
+
}
|
|
11839
|
+
}
|
|
11840
|
+
|
|
11841
|
+
// server/static.ts
|
|
11842
|
+
import { existsSync as existsSync16 } from "fs";
|
|
11843
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
11844
|
+
import { extname, join as join13 } from "path";
|
|
11845
|
+
import { serveStatic } from "@hono/node-server/serve-static";
|
|
11846
|
+
var INDEX_HTML = "index.html";
|
|
11847
|
+
var PLACEHOLDER_HTML = `<!doctype html>
|
|
11848
|
+
<html lang="en">
|
|
11849
|
+
<head>
|
|
11850
|
+
<meta charset="utf-8" />
|
|
11851
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
11852
|
+
<meta name="skill-map-mode" content="live" />
|
|
11853
|
+
<title>skill-map server</title>
|
|
11854
|
+
<style>
|
|
11855
|
+
body { font-family: system-ui, sans-serif; margin: 2rem; max-width: 36rem; line-height: 1.5; }
|
|
11856
|
+
code { background: #f4f4f4; padding: 0.1rem 0.3rem; border-radius: 3px; }
|
|
11857
|
+
h1 { font-size: 1.4rem; }
|
|
11858
|
+
</style>
|
|
11859
|
+
</head>
|
|
11860
|
+
<body>
|
|
11861
|
+
<h1>skill-map server is running</h1>
|
|
11862
|
+
<p>The UI bundle was not found. Run <code>npm run build --workspace=ui</code> from the repo root, or pass <code>--ui-dist <path></code>, then restart <code>sm serve</code>.</p>
|
|
11863
|
+
<p>The REST API is available at <code>/api/health</code>.</p>
|
|
11864
|
+
</body>
|
|
11865
|
+
</html>
|
|
11866
|
+
`;
|
|
11867
|
+
function createStaticHandler(uiDist) {
|
|
11868
|
+
if (uiDist === null) return placeholderRootMiddleware();
|
|
11869
|
+
return serveStatic({ root: uiDist });
|
|
11870
|
+
}
|
|
11871
|
+
function createSpaFallback(uiDist) {
|
|
11872
|
+
return async (c, _next) => {
|
|
11873
|
+
if (c.req.method !== "GET" && c.req.method !== "HEAD") return c.notFound();
|
|
11874
|
+
if (uiDist === null) return htmlResponse(c, PLACEHOLDER_HTML);
|
|
11875
|
+
const indexPath = join13(uiDist, INDEX_HTML);
|
|
11876
|
+
if (!existsSync16(indexPath)) return htmlResponse(c, PLACEHOLDER_HTML);
|
|
11877
|
+
return fileResponse(c, indexPath);
|
|
11878
|
+
};
|
|
11879
|
+
}
|
|
11880
|
+
function placeholderRootMiddleware() {
|
|
11881
|
+
return async (c, next) => {
|
|
11882
|
+
if (c.req.method !== "GET" && c.req.method !== "HEAD") return next();
|
|
11883
|
+
if (c.req.path === "/" || c.req.path === "/index.html") {
|
|
11884
|
+
return htmlResponse(c, PLACEHOLDER_HTML);
|
|
11885
|
+
}
|
|
11886
|
+
return next();
|
|
11887
|
+
};
|
|
11888
|
+
}
|
|
11889
|
+
var MIME_TYPES = {
|
|
11890
|
+
".html": "text/html; charset=UTF-8",
|
|
11891
|
+
".js": "application/javascript; charset=UTF-8",
|
|
11892
|
+
".mjs": "application/javascript; charset=UTF-8",
|
|
11893
|
+
".css": "text/css; charset=UTF-8",
|
|
11894
|
+
".json": "application/json; charset=UTF-8",
|
|
11895
|
+
".svg": "image/svg+xml",
|
|
11896
|
+
".png": "image/png",
|
|
11897
|
+
".jpg": "image/jpeg",
|
|
11898
|
+
".jpeg": "image/jpeg",
|
|
11899
|
+
".gif": "image/gif",
|
|
11900
|
+
".webp": "image/webp",
|
|
11901
|
+
".ico": "image/x-icon",
|
|
11902
|
+
".woff": "font/woff",
|
|
11903
|
+
".woff2": "font/woff2",
|
|
11904
|
+
".ttf": "font/ttf",
|
|
11905
|
+
".otf": "font/otf",
|
|
11906
|
+
".txt": "text/plain; charset=UTF-8",
|
|
11907
|
+
".map": "application/json; charset=UTF-8"
|
|
11908
|
+
};
|
|
11909
|
+
function mimeFor(filePath) {
|
|
11910
|
+
const ext = extname(filePath).toLowerCase();
|
|
11911
|
+
return MIME_TYPES[ext] ?? "application/octet-stream";
|
|
11912
|
+
}
|
|
11913
|
+
function htmlResponse(c, html) {
|
|
11914
|
+
return c.body(html, 200, { "content-type": "text/html; charset=UTF-8" });
|
|
11915
|
+
}
|
|
11916
|
+
async function fileResponse(c, absPath) {
|
|
11917
|
+
const buf = await readFile4(absPath);
|
|
11918
|
+
return c.body(buf, 200, { "content-type": mimeFor(absPath) });
|
|
11919
|
+
}
|
|
11920
|
+
|
|
11921
|
+
// server/app.ts
|
|
11922
|
+
function createApp(deps) {
|
|
11923
|
+
const app = new Hono();
|
|
11924
|
+
if (deps.options.devCors) {
|
|
11925
|
+
app.use("*", async (c, next) => {
|
|
11926
|
+
await next();
|
|
11927
|
+
c.res.headers.set("access-control-allow-origin", "*");
|
|
11928
|
+
c.res.headers.set("access-control-allow-methods", "GET,POST,PUT,DELETE,OPTIONS");
|
|
11929
|
+
c.res.headers.set("access-control-allow-headers", "content-type, authorization");
|
|
11930
|
+
});
|
|
11931
|
+
app.options("*", (c) => c.body(null, 204));
|
|
11932
|
+
}
|
|
11933
|
+
app.get("/api/health", (c) => {
|
|
11934
|
+
const payload = buildHealth({
|
|
11935
|
+
dbPath: deps.options.dbPath,
|
|
11936
|
+
scope: deps.options.scope,
|
|
11937
|
+
specVersion: deps.specVersion
|
|
11938
|
+
});
|
|
11939
|
+
return c.json(payload);
|
|
11940
|
+
});
|
|
11941
|
+
app.all("/api/*", (c) => {
|
|
11942
|
+
throw new HTTPException(404, { message: `Unknown API endpoint: ${c.req.path}` });
|
|
11943
|
+
});
|
|
11944
|
+
deps.attachWs(app);
|
|
11945
|
+
app.use("*", createStaticHandler(deps.options.uiDist));
|
|
11946
|
+
app.get("*", createSpaFallback(deps.options.uiDist));
|
|
11947
|
+
app.notFound((c) => {
|
|
11948
|
+
throw new HTTPException(404, { message: `Not found: ${c.req.path}` });
|
|
11949
|
+
});
|
|
11950
|
+
app.onError((err, c) => {
|
|
11951
|
+
return formatError2(err, c);
|
|
11952
|
+
});
|
|
11953
|
+
return app;
|
|
11954
|
+
}
|
|
11955
|
+
function codeForStatus(status) {
|
|
11956
|
+
if (status === 404) return "not-found";
|
|
11957
|
+
if (status === 400) return "bad-query";
|
|
11958
|
+
return "internal";
|
|
11959
|
+
}
|
|
11960
|
+
function formatError2(err, c) {
|
|
11961
|
+
if (err instanceof HTTPException) {
|
|
11962
|
+
const status = err.status;
|
|
11963
|
+
const envelope2 = {
|
|
11964
|
+
ok: false,
|
|
11965
|
+
error: {
|
|
11966
|
+
code: codeForStatus(status),
|
|
11967
|
+
message: err.message,
|
|
11968
|
+
details: null
|
|
11969
|
+
}
|
|
11970
|
+
};
|
|
11971
|
+
return c.json(envelope2, status);
|
|
11972
|
+
}
|
|
11973
|
+
const envelope = {
|
|
11974
|
+
ok: false,
|
|
11975
|
+
error: {
|
|
11976
|
+
code: "internal",
|
|
11977
|
+
message: formatErrorMessage(err),
|
|
11978
|
+
details: null
|
|
11979
|
+
}
|
|
11980
|
+
};
|
|
11981
|
+
return c.json(envelope, 500);
|
|
11982
|
+
}
|
|
11983
|
+
|
|
11984
|
+
// server/ws.ts
|
|
11985
|
+
import { upgradeWebSocket } from "@hono/node-server";
|
|
11986
|
+
var WS_PATH = "/ws";
|
|
11987
|
+
var NOOP_CLOSE_CODE = 1e3;
|
|
11988
|
+
var NOOP_CLOSE_REASON = "no broadcaster yet";
|
|
11989
|
+
function noopWebSocketRoute(app) {
|
|
11990
|
+
app.get(
|
|
11991
|
+
WS_PATH,
|
|
11992
|
+
upgradeWebSocket(() => ({
|
|
11993
|
+
onOpen(_event, ws) {
|
|
11994
|
+
ws.close(NOOP_CLOSE_CODE, NOOP_CLOSE_REASON);
|
|
11995
|
+
}
|
|
11996
|
+
}))
|
|
11997
|
+
);
|
|
11998
|
+
}
|
|
11999
|
+
|
|
12000
|
+
// server/options.ts
|
|
12001
|
+
var DEFAULT_PORT = 4242;
|
|
12002
|
+
var DEFAULT_HOST = "127.0.0.1";
|
|
12003
|
+
var DEFAULT_SCOPE = "project";
|
|
12004
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "::1", "localhost", "0:0:0:0:0:0:0:1"]);
|
|
12005
|
+
function isLoopbackHost(host) {
|
|
12006
|
+
return LOOPBACK_HOSTS.has(host.toLowerCase());
|
|
12007
|
+
}
|
|
12008
|
+
function validateServerOptions(input) {
|
|
12009
|
+
const filled = applyDefaults(input);
|
|
12010
|
+
const portError = validatePort(filled.port);
|
|
12011
|
+
if (portError !== null) return { ok: false, error: portError };
|
|
12012
|
+
const scopeError = validateScope(filled.scope);
|
|
12013
|
+
if (scopeError !== null) return { ok: false, error: scopeError };
|
|
12014
|
+
const hostError = validateHost(filled.host, filled.devCors);
|
|
12015
|
+
if (hostError !== null) return { ok: false, error: hostError };
|
|
12016
|
+
return {
|
|
12017
|
+
ok: true,
|
|
12018
|
+
options: {
|
|
12019
|
+
port: filled.port,
|
|
12020
|
+
host: filled.host,
|
|
12021
|
+
scope: filled.scope,
|
|
12022
|
+
dbPath: input.dbPath,
|
|
12023
|
+
uiDist: filled.uiDist,
|
|
12024
|
+
noBuiltIns: filled.noBuiltIns,
|
|
12025
|
+
noPlugins: filled.noPlugins,
|
|
12026
|
+
open: filled.open,
|
|
12027
|
+
devCors: filled.devCors
|
|
12028
|
+
}
|
|
12029
|
+
};
|
|
12030
|
+
}
|
|
12031
|
+
function applyDefaults(input) {
|
|
12032
|
+
return {
|
|
12033
|
+
port: input.port ?? DEFAULT_PORT,
|
|
12034
|
+
host: input.host ?? DEFAULT_HOST,
|
|
12035
|
+
scope: input.scope ?? DEFAULT_SCOPE,
|
|
12036
|
+
uiDist: input.uiDist ?? null,
|
|
12037
|
+
noBuiltIns: input.noBuiltIns ?? false,
|
|
12038
|
+
noPlugins: input.noPlugins ?? false,
|
|
12039
|
+
open: input.open ?? true,
|
|
12040
|
+
devCors: input.devCors ?? false
|
|
12041
|
+
};
|
|
12042
|
+
}
|
|
12043
|
+
function validatePort(port) {
|
|
12044
|
+
if (!Number.isInteger(port)) {
|
|
12045
|
+
return { code: "port-invalid", message: `port must be an integer (got ${port})`, value: String(port) };
|
|
12046
|
+
}
|
|
12047
|
+
if (port < 0 || port > 65535) {
|
|
12048
|
+
return {
|
|
12049
|
+
code: "port-out-of-range",
|
|
12050
|
+
message: `port must be in [0, 65535] (got ${port})`,
|
|
12051
|
+
value: String(port)
|
|
12052
|
+
};
|
|
12053
|
+
}
|
|
12054
|
+
return null;
|
|
12055
|
+
}
|
|
12056
|
+
function validateScope(scope) {
|
|
12057
|
+
if (scope !== "project" && scope !== "global") {
|
|
12058
|
+
return { code: "scope-invalid", message: `scope must be "project" or "global"`, value: String(scope) };
|
|
12059
|
+
}
|
|
12060
|
+
return null;
|
|
12061
|
+
}
|
|
12062
|
+
function validateHost(host, devCors) {
|
|
12063
|
+
if (devCors && !isLoopbackHost(host)) {
|
|
12064
|
+
return {
|
|
12065
|
+
code: "host-dev-cors-rejected",
|
|
12066
|
+
message: `--dev-cors requires a loopback --host (got ${host})`,
|
|
12067
|
+
value: host
|
|
12068
|
+
};
|
|
12069
|
+
}
|
|
12070
|
+
return null;
|
|
12071
|
+
}
|
|
12072
|
+
|
|
12073
|
+
// server/paths.ts
|
|
12074
|
+
import { existsSync as existsSync17, statSync as statSync5 } from "fs";
|
|
12075
|
+
import { dirname as dirname9, isAbsolute as isAbsolute4, join as join14, resolve as resolve19 } from "path";
|
|
12076
|
+
var DEFAULT_UI_REL = join14("ui", "dist", "browser");
|
|
12077
|
+
var INDEX_HTML2 = "index.html";
|
|
12078
|
+
function resolveDefaultUiDist(ctx) {
|
|
12079
|
+
let current = resolve19(ctx.cwd);
|
|
12080
|
+
for (let i = 0; i < 64; i++) {
|
|
12081
|
+
const candidate = join14(current, DEFAULT_UI_REL);
|
|
12082
|
+
if (isUiBundleDir(candidate)) return candidate;
|
|
12083
|
+
const parent = dirname9(current);
|
|
12084
|
+
if (parent === current) return null;
|
|
12085
|
+
current = parent;
|
|
12086
|
+
}
|
|
12087
|
+
return null;
|
|
12088
|
+
}
|
|
12089
|
+
function resolveExplicitUiDist(ctx, raw) {
|
|
12090
|
+
return isAbsolute4(raw) ? raw : resolve19(ctx.cwd, raw);
|
|
12091
|
+
}
|
|
12092
|
+
function isUiBundleDir(path) {
|
|
12093
|
+
if (!existsSync17(path)) return false;
|
|
12094
|
+
try {
|
|
12095
|
+
if (!statSync5(path).isDirectory()) return false;
|
|
12096
|
+
return existsSync17(join14(path, INDEX_HTML2));
|
|
12097
|
+
} catch {
|
|
12098
|
+
return false;
|
|
12099
|
+
}
|
|
12100
|
+
}
|
|
12101
|
+
|
|
12102
|
+
// server/index.ts
|
|
12103
|
+
async function createServer(options) {
|
|
12104
|
+
const specVersion = await resolveSpecVersion2();
|
|
12105
|
+
const app = createApp({ options, specVersion, attachWs: noopWebSocketRoute });
|
|
12106
|
+
const wss = new WebSocketServer({ noServer: true });
|
|
12107
|
+
const server = await listenAsync(app.fetch, wss, options.host, options.port);
|
|
12108
|
+
const addr = server.address();
|
|
12109
|
+
const address = normalizeAddress(addr, options.host, options.port);
|
|
12110
|
+
let closed = false;
|
|
12111
|
+
const close = async () => {
|
|
12112
|
+
if (closed) return;
|
|
12113
|
+
closed = true;
|
|
12114
|
+
await closeServer(server);
|
|
12115
|
+
wss.close();
|
|
12116
|
+
};
|
|
12117
|
+
return { address, close };
|
|
12118
|
+
}
|
|
12119
|
+
function listenAsync(fetchCallback, wss, host, port) {
|
|
12120
|
+
return new Promise((resolveListen, rejectListen) => {
|
|
12121
|
+
let settled = false;
|
|
12122
|
+
const server = serve(
|
|
12123
|
+
{
|
|
12124
|
+
fetch: fetchCallback,
|
|
12125
|
+
hostname: host,
|
|
12126
|
+
port,
|
|
12127
|
+
websocket: { server: wss }
|
|
12128
|
+
},
|
|
12129
|
+
() => {
|
|
12130
|
+
if (settled) return;
|
|
12131
|
+
settled = true;
|
|
12132
|
+
server.removeListener("error", onBindError);
|
|
12133
|
+
resolveListen(server);
|
|
12134
|
+
}
|
|
12135
|
+
);
|
|
12136
|
+
const onBindError = (err) => {
|
|
12137
|
+
if (settled) return;
|
|
12138
|
+
settled = true;
|
|
12139
|
+
rejectListen(err);
|
|
12140
|
+
};
|
|
12141
|
+
server.once("error", onBindError);
|
|
12142
|
+
});
|
|
12143
|
+
}
|
|
12144
|
+
function closeServer(server) {
|
|
12145
|
+
return new Promise((resolveClose, rejectClose) => {
|
|
12146
|
+
server.close((err) => {
|
|
12147
|
+
if (err) {
|
|
12148
|
+
rejectClose(err);
|
|
12149
|
+
} else {
|
|
12150
|
+
resolveClose();
|
|
12151
|
+
}
|
|
12152
|
+
});
|
|
12153
|
+
server.closeAllConnections?.();
|
|
12154
|
+
});
|
|
12155
|
+
}
|
|
12156
|
+
function normalizeAddress(addr, fallbackHost, fallbackPort) {
|
|
12157
|
+
if (addr === null || typeof addr === "string") {
|
|
12158
|
+
return { host: fallbackHost, port: fallbackPort, family: "IPv4" };
|
|
12159
|
+
}
|
|
12160
|
+
return { host: addr.address, port: addr.port, family: addr.family };
|
|
12161
|
+
}
|
|
12162
|
+
|
|
12163
|
+
// cli/i18n/serve.texts.ts
|
|
12164
|
+
var SERVE_TEXTS = {
|
|
12165
|
+
// Banner emitted to stderr after the listener binds. Mirrors `sm watch`
|
|
12166
|
+
// by writing operational status to stderr (stdout is reserved for
|
|
12167
|
+
// future `--json` boot payloads).
|
|
12168
|
+
boot: "sm serve: listening on http://{{host}}:{{port}} (scope={{scope}}, db={{db}})\n",
|
|
12169
|
+
// Hint shown after the boot line. Branches on --open: when auto-open
|
|
12170
|
+
// is on (default), the message states intent ("opening …"); when
|
|
12171
|
+
// --no-open, it instructs the user to visit the URL manually.
|
|
12172
|
+
// Both end with the Ctrl+C reminder so the operational tail is
|
|
12173
|
+
// identical regardless of branch.
|
|
12174
|
+
bootOpening: "sm serve: opening http://{{host}}:{{port}}/ in your browser. Press Ctrl+C to stop.\n",
|
|
12175
|
+
bootVisitHint: "sm serve: visit http://{{host}}:{{port}}/ in your browser. Press Ctrl+C to stop.\n",
|
|
12176
|
+
// Browser-open failure. Non-fatal — the URL is already printed; the
|
|
12177
|
+
// user can open it manually.
|
|
12178
|
+
openFailed: "sm serve: could not auto-open browser ({{message}}). Visit {{url}} manually.\n",
|
|
12179
|
+
// Bind failure (port in use, EACCES, etc.) → ExitCode.Error.
|
|
12180
|
+
bindFailed: "sm serve: failed to bind {{host}}:{{port}} \u2014 {{message}}\n",
|
|
12181
|
+
// Flag-validation failures — ExitCode.Error.
|
|
12182
|
+
hostDevCorsRejected: "sm serve: --dev-cors requires a loopback --host (got {{host}}). Refusing per Decision #119.\n",
|
|
12183
|
+
portOutOfRange: "sm serve: --port must be an integer in [0, 65535] (got {{value}}).\n",
|
|
12184
|
+
portInvalid: "sm serve: --port must be a non-negative integer (got {{value}}).\n",
|
|
12185
|
+
scopeInvalid: 'sm serve: --scope must be "project" or "global" (got {{value}}).\n',
|
|
12186
|
+
// Generic operational error — surfaced when the server itself throws
|
|
12187
|
+
// before the listener binds (e.g. UI bundle missing under explicit
|
|
12188
|
+
// --ui-dist).
|
|
12189
|
+
startupFailed: "sm serve: startup failed \u2014 {{message}}\n",
|
|
12190
|
+
// DB-not-found (--db <path> doesn't exist) → ExitCode.NotFound.
|
|
12191
|
+
dbNotFound: "sm serve: --db {{path}} does not exist.\n",
|
|
12192
|
+
// Shutdown trace — printed once the listener has closed.
|
|
12193
|
+
shutdown: "sm serve: shutdown complete.\n"
|
|
12194
|
+
};
|
|
12195
|
+
|
|
12196
|
+
// cli/commands/serve.ts
|
|
12197
|
+
var ServeCommand = class extends SmCommand {
|
|
12198
|
+
static paths = [["serve"]];
|
|
12199
|
+
static usage = Command19.Usage({
|
|
12200
|
+
category: "Setup",
|
|
12201
|
+
description: "Start the Hono BFF (single-port: REST + WebSocket + SPA bundle).",
|
|
12202
|
+
details: `
|
|
12203
|
+
Boots the skill-map Web UI's backing server. One Node process
|
|
12204
|
+
serves the Angular SPA, the REST API under /api/*, and the
|
|
12205
|
+
WebSocket at /ws \u2014 single-port mandate, no proxy.
|
|
12206
|
+
|
|
12207
|
+
Default port is 4242, default host is 127.0.0.1. The server boots
|
|
12208
|
+
even when the project DB is missing \u2014 /api/health reports
|
|
12209
|
+
'db: missing' so the SPA renders an empty-state CTA instead of
|
|
12210
|
+
failing the connection.
|
|
12211
|
+
|
|
12212
|
+
Loopback-only assumption through v0.6.0 (no per-connection auth on
|
|
12213
|
+
/ws). Combining --dev-cors with a non-loopback --host is rejected.
|
|
12214
|
+
|
|
12215
|
+
SIGINT / SIGTERM trigger a graceful shutdown.
|
|
12216
|
+
`,
|
|
12217
|
+
examples: [
|
|
12218
|
+
["Start on the default port and open the browser", "$0 serve"],
|
|
12219
|
+
["Custom port, no browser auto-open", "$0 serve --port 5000 --no-open"],
|
|
12220
|
+
["Use the global scope DB", "$0 serve --scope global"],
|
|
12221
|
+
["Point at a pre-built UI bundle", "$0 serve --ui-dist ./ui/dist/browser"]
|
|
12222
|
+
]
|
|
12223
|
+
});
|
|
12224
|
+
port = Option19.String("--port", {
|
|
12225
|
+
required: false,
|
|
12226
|
+
description: "Listening port (default 4242). 0 = OS-assigned."
|
|
12227
|
+
});
|
|
12228
|
+
host = Option19.String("--host", {
|
|
12229
|
+
required: false,
|
|
12230
|
+
description: "Listening host (default 127.0.0.1). Loopback-only enforced when --dev-cors is set."
|
|
12231
|
+
});
|
|
12232
|
+
scope = Option19.String("--scope", {
|
|
12233
|
+
required: false,
|
|
12234
|
+
description: "project | global. Alias for -g/--global. Default: project."
|
|
12235
|
+
});
|
|
12236
|
+
noBuiltIns = Option19.Boolean("--no-built-ins", false, {
|
|
12237
|
+
description: "Skip built-in plugin registration (parity with sm scan --no-built-ins)."
|
|
12238
|
+
});
|
|
12239
|
+
noPlugins = Option19.Boolean("--no-plugins", false, {
|
|
12240
|
+
description: "Skip drop-in plugin discovery."
|
|
12241
|
+
});
|
|
12242
|
+
// `Option.Boolean('--open', true)` — Clipanion's parser auto-derives
|
|
12243
|
+
// the `--no-open` inverse for every boolean flag (search for
|
|
12244
|
+
// `--no-${name.slice(2)}` in clipanion's core), so the explicit
|
|
12245
|
+
// `--no-open` descriptor must NOT be declared here or the parser sees
|
|
12246
|
+
// two registrations for the same flag and rejects the invocation
|
|
12247
|
+
// with "Ambiguous Syntax Error". Same convention shipped by every
|
|
12248
|
+
// other `--no-...` flag in the CLI tree.
|
|
12249
|
+
open = Option19.Boolean("--open", true, {
|
|
12250
|
+
description: "Auto-open the SPA in the user's default browser after listen. --no-open opts out."
|
|
12251
|
+
});
|
|
12252
|
+
devCors = Option19.Boolean("--dev-cors", false, {
|
|
12253
|
+
description: "Enable permissive CORS for the Angular dev-server proxy workflow."
|
|
12254
|
+
});
|
|
12255
|
+
// `--ui-dist` is intentionally undocumented in the Usage block above
|
|
12256
|
+
// (the demo build pipeline + tests rely on it; everyday users never
|
|
12257
|
+
// need it). Clipanion still exposes it on the parser; the Usage
|
|
12258
|
+
// omission is the "hidden" contract per the 14.1 brief.
|
|
12259
|
+
uiDist = Option19.String("--ui-dist", { required: false, hidden: true });
|
|
12260
|
+
// Long-running daemon — `done in <…>` after a graceful shutdown is
|
|
12261
|
+
// noise. Mirrors `sm watch`'s opt-out.
|
|
12262
|
+
emitElapsed = false;
|
|
12263
|
+
// CLI orchestrator with multi-flag handling — each `if (this.flag)`
|
|
12264
|
+
// branch is one cyclomatic point. Splitting per branch scatters the
|
|
12265
|
+
// validation away from the flag it gates. Per AGENTS.md §Linting
|
|
12266
|
+
// category 1 ("CLI orchestrators with multi-flag handling").
|
|
12267
|
+
// eslint-disable-next-line complexity
|
|
12268
|
+
async run() {
|
|
12269
|
+
const runtimeCtx = defaultRuntimeContext();
|
|
12270
|
+
const scopeResult = resolveScope(this.scope, this.global);
|
|
12271
|
+
if (!scopeResult.ok) {
|
|
12272
|
+
this.context.stderr.write(
|
|
12273
|
+
tx(SERVE_TEXTS.scopeInvalid, { value: sanitizeForTerminal(scopeResult.value) })
|
|
12274
|
+
);
|
|
12275
|
+
return ExitCode.Error;
|
|
12276
|
+
}
|
|
12277
|
+
const scope = scopeResult.scope;
|
|
12278
|
+
if (scope === "global") this.global = true;
|
|
12279
|
+
const portResult = parsePort(this.port);
|
|
12280
|
+
if (!portResult.ok) {
|
|
12281
|
+
this.context.stderr.write(
|
|
12282
|
+
tx(SERVE_TEXTS.portInvalid, { value: sanitizeForTerminal(portResult.value) })
|
|
12283
|
+
);
|
|
12284
|
+
return ExitCode.Error;
|
|
12285
|
+
}
|
|
12286
|
+
const dbPath = resolveDbPath({ global: this.global, db: this.db, ...runtimeCtx });
|
|
12287
|
+
if (this.db !== void 0 && !existsSync18(dbPath)) {
|
|
12288
|
+
this.context.stderr.write(
|
|
12289
|
+
tx(SERVE_TEXTS.dbNotFound, { path: sanitizeForTerminal(dbPath) })
|
|
12290
|
+
);
|
|
12291
|
+
return ExitCode.NotFound;
|
|
12292
|
+
}
|
|
12293
|
+
const uiDistResult = resolveUiDist(runtimeCtx, this.uiDist);
|
|
12294
|
+
if (!uiDistResult.ok) {
|
|
12295
|
+
this.context.stderr.write(
|
|
12296
|
+
tx(SERVE_TEXTS.startupFailed, { message: sanitizeForTerminal(uiDistResult.message) })
|
|
12297
|
+
);
|
|
12298
|
+
return ExitCode.Error;
|
|
12299
|
+
}
|
|
12300
|
+
const input = {
|
|
12301
|
+
dbPath,
|
|
12302
|
+
scope,
|
|
12303
|
+
uiDist: uiDistResult.uiDist,
|
|
12304
|
+
noBuiltIns: this.noBuiltIns,
|
|
12305
|
+
noPlugins: this.noPlugins,
|
|
12306
|
+
open: this.open,
|
|
12307
|
+
devCors: this.devCors
|
|
12308
|
+
};
|
|
12309
|
+
if (portResult.port !== void 0) input.port = portResult.port;
|
|
12310
|
+
if (this.host !== void 0) input.host = this.host;
|
|
12311
|
+
const validation = validateServerOptions(input);
|
|
12312
|
+
if (!validation.ok) {
|
|
12313
|
+
this.context.stderr.write(formatValidationError(validation.error));
|
|
12314
|
+
return ExitCode.Error;
|
|
12315
|
+
}
|
|
12316
|
+
let handle;
|
|
12317
|
+
try {
|
|
12318
|
+
handle = await createServer(validation.options);
|
|
12319
|
+
} catch (err) {
|
|
12320
|
+
const message = formatErrorMessage(err);
|
|
12321
|
+
this.context.stderr.write(
|
|
12322
|
+
tx(SERVE_TEXTS.bindFailed, {
|
|
12323
|
+
host: sanitizeForTerminal(validation.options.host),
|
|
12324
|
+
port: validation.options.port,
|
|
12325
|
+
message: sanitizeForTerminal(message)
|
|
12326
|
+
})
|
|
12327
|
+
);
|
|
12328
|
+
return ExitCode.Error;
|
|
12329
|
+
}
|
|
12330
|
+
this.context.stderr.write(
|
|
12331
|
+
tx(SERVE_TEXTS.boot, {
|
|
12332
|
+
host: sanitizeForTerminal(handle.address.host),
|
|
12333
|
+
port: handle.address.port,
|
|
12334
|
+
scope,
|
|
12335
|
+
db: sanitizeForTerminal(dbPath)
|
|
12336
|
+
})
|
|
12337
|
+
);
|
|
12338
|
+
this.context.stderr.write(
|
|
12339
|
+
tx(validation.options.open ? SERVE_TEXTS.bootOpening : SERVE_TEXTS.bootVisitHint, {
|
|
12340
|
+
host: sanitizeForTerminal(handle.address.host),
|
|
12341
|
+
port: handle.address.port
|
|
12342
|
+
})
|
|
12343
|
+
);
|
|
12344
|
+
if (validation.options.open) {
|
|
12345
|
+
const url = `http://${handle.address.host}:${handle.address.port}/`;
|
|
12346
|
+
tryOpenBrowser(url, this.context.stderr);
|
|
12347
|
+
}
|
|
12348
|
+
await waitForShutdown();
|
|
12349
|
+
await handle.close();
|
|
12350
|
+
this.context.stderr.write(SERVE_TEXTS.shutdown);
|
|
12351
|
+
return ExitCode.Ok;
|
|
12352
|
+
}
|
|
12353
|
+
};
|
|
12354
|
+
function parsePort(raw) {
|
|
12355
|
+
if (raw === void 0) return { ok: true, port: void 0 };
|
|
12356
|
+
const trimmed = raw.trim();
|
|
12357
|
+
const parsed = Number.parseInt(trimmed, 10);
|
|
12358
|
+
if (!Number.isInteger(parsed) || parsed < 0 || String(parsed) !== trimmed) {
|
|
12359
|
+
return { ok: false, value: raw };
|
|
12360
|
+
}
|
|
12361
|
+
return { ok: true, port: parsed };
|
|
12362
|
+
}
|
|
12363
|
+
function resolveScope(rawScope, global) {
|
|
12364
|
+
if (rawScope === void 0) return { ok: true, scope: global ? "global" : "project" };
|
|
12365
|
+
if (rawScope === "project" || rawScope === "global") {
|
|
12366
|
+
return { ok: true, scope: rawScope };
|
|
12367
|
+
}
|
|
12368
|
+
return { ok: false, value: rawScope };
|
|
12369
|
+
}
|
|
12370
|
+
function resolveUiDist(ctx, raw) {
|
|
12371
|
+
if (raw === void 0) {
|
|
12372
|
+
return { ok: true, uiDist: resolveDefaultUiDist(ctx) };
|
|
12373
|
+
}
|
|
12374
|
+
const abs = resolveExplicitUiDist(ctx, raw);
|
|
12375
|
+
if (!isUiBundleDir(abs)) {
|
|
12376
|
+
return {
|
|
12377
|
+
ok: false,
|
|
12378
|
+
message: `--ui-dist ${abs} does not exist or is not a directory containing index.html`
|
|
12379
|
+
};
|
|
12380
|
+
}
|
|
12381
|
+
return { ok: true, uiDist: abs };
|
|
12382
|
+
}
|
|
12383
|
+
function formatValidationError(err) {
|
|
12384
|
+
switch (err.code) {
|
|
12385
|
+
case "host-dev-cors-rejected":
|
|
12386
|
+
return tx(SERVE_TEXTS.hostDevCorsRejected, { host: sanitizeForTerminal(err.value) });
|
|
12387
|
+
case "port-out-of-range":
|
|
12388
|
+
return tx(SERVE_TEXTS.portOutOfRange, { value: sanitizeForTerminal(err.value) });
|
|
12389
|
+
case "port-invalid":
|
|
12390
|
+
return tx(SERVE_TEXTS.portInvalid, { value: sanitizeForTerminal(err.value) });
|
|
12391
|
+
case "scope-invalid":
|
|
12392
|
+
return tx(SERVE_TEXTS.scopeInvalid, { value: sanitizeForTerminal(err.value) });
|
|
12393
|
+
default:
|
|
12394
|
+
return tx(SERVE_TEXTS.startupFailed, { message: sanitizeForTerminal(err.message) });
|
|
12395
|
+
}
|
|
12396
|
+
}
|
|
12397
|
+
function waitForShutdown() {
|
|
12398
|
+
return new Promise((resolveShutdown) => {
|
|
12399
|
+
const onSignal = () => {
|
|
12400
|
+
process.removeListener("SIGINT", onSignal);
|
|
12401
|
+
process.removeListener("SIGTERM", onSignal);
|
|
12402
|
+
resolveShutdown();
|
|
12403
|
+
};
|
|
12404
|
+
process.once("SIGINT", onSignal);
|
|
12405
|
+
process.once("SIGTERM", onSignal);
|
|
12406
|
+
});
|
|
12407
|
+
}
|
|
12408
|
+
function tryOpenBrowser(url, stderr) {
|
|
12409
|
+
try {
|
|
12410
|
+
const platform = process.platform;
|
|
12411
|
+
let command;
|
|
12412
|
+
let args2;
|
|
12413
|
+
if (platform === "darwin") {
|
|
12414
|
+
command = "open";
|
|
12415
|
+
args2 = [url];
|
|
12416
|
+
} else if (platform === "win32") {
|
|
12417
|
+
command = "cmd";
|
|
12418
|
+
args2 = ["/c", "start", '""', url];
|
|
12419
|
+
} else {
|
|
12420
|
+
command = "xdg-open";
|
|
12421
|
+
args2 = [url];
|
|
12422
|
+
}
|
|
12423
|
+
const child = spawn(command, args2, { detached: true, stdio: "ignore" });
|
|
12424
|
+
child.on("error", (err) => {
|
|
12425
|
+
stderr.write(
|
|
12426
|
+
tx(SERVE_TEXTS.openFailed, {
|
|
12427
|
+
message: sanitizeForTerminal(formatErrorMessage(err)),
|
|
12428
|
+
url: sanitizeForTerminal(url)
|
|
12429
|
+
})
|
|
12430
|
+
);
|
|
12431
|
+
});
|
|
12432
|
+
child.unref();
|
|
12433
|
+
} catch (err) {
|
|
12434
|
+
stderr.write(
|
|
12435
|
+
tx(SERVE_TEXTS.openFailed, {
|
|
12436
|
+
message: sanitizeForTerminal(formatErrorMessage(err)),
|
|
12437
|
+
url: sanitizeForTerminal(url)
|
|
12438
|
+
})
|
|
12439
|
+
);
|
|
12440
|
+
}
|
|
12441
|
+
}
|
|
12442
|
+
|
|
11722
12443
|
// cli/commands/show.ts
|
|
11723
|
-
import { Command as
|
|
12444
|
+
import { Command as Command20, Option as Option20 } from "clipanion";
|
|
11724
12445
|
|
|
11725
12446
|
// cli/i18n/show.texts.ts
|
|
11726
12447
|
var SHOW_TEXTS = {
|
|
@@ -11758,16 +12479,16 @@ var SHOW_TEXTS = {
|
|
|
11758
12479
|
};
|
|
11759
12480
|
|
|
11760
12481
|
// cli/commands/show.ts
|
|
11761
|
-
var ShowCommand = class extends
|
|
12482
|
+
var ShowCommand = class extends SmCommand {
|
|
11762
12483
|
static paths = [["show"]];
|
|
11763
|
-
static usage =
|
|
12484
|
+
static usage = Command20.Usage({
|
|
11764
12485
|
category: "Browse",
|
|
11765
|
-
description: "Node detail: weight, frontmatter, links, issues
|
|
12486
|
+
description: "Node detail: weight, frontmatter, links, issues.",
|
|
11766
12487
|
details: `
|
|
11767
12488
|
Loads a single node from the persisted snapshot, plus every link
|
|
11768
|
-
(in and out) and every current issue touching it.
|
|
11769
|
-
|
|
11770
|
-
|
|
12489
|
+
(in and out) and every current issue touching it. Step 10
|
|
12490
|
+
(findings) and Step 11 (summary) will add fields when their
|
|
12491
|
+
backing tables ship.
|
|
11771
12492
|
|
|
11772
12493
|
Run \`sm scan\` first to populate the DB.
|
|
11773
12494
|
`,
|
|
@@ -11776,11 +12497,8 @@ var ShowCommand = class extends Command18 {
|
|
|
11776
12497
|
["Machine-readable detail", "$0 show .claude/agents/architect.md --json"]
|
|
11777
12498
|
]
|
|
11778
12499
|
});
|
|
11779
|
-
nodePath =
|
|
11780
|
-
|
|
11781
|
-
db = Option18.String("--db", { required: false });
|
|
11782
|
-
json = Option18.Boolean("--json", false);
|
|
11783
|
-
async execute() {
|
|
12500
|
+
nodePath = Option20.String({ required: true });
|
|
12501
|
+
async run() {
|
|
11784
12502
|
const dbPath = resolveDbPath({ global: this.global, db: this.db, ...defaultRuntimeContext() });
|
|
11785
12503
|
if (!assertDbExists(dbPath, this.context.stderr)) return ExitCode.NotFound;
|
|
11786
12504
|
return withSqlite({ databasePath: dbPath, autoBackup: false }, async (adapter) => {
|
|
@@ -11793,9 +12511,7 @@ var ShowCommand = class extends Command18 {
|
|
|
11793
12511
|
node: bundle.node,
|
|
11794
12512
|
linksOut: bundle.linksOut,
|
|
11795
12513
|
linksIn: bundle.linksIn,
|
|
11796
|
-
issues: bundle.issues
|
|
11797
|
-
findings: [],
|
|
11798
|
-
summary: null
|
|
12514
|
+
issues: bundle.issues
|
|
11799
12515
|
};
|
|
11800
12516
|
if (this.json) {
|
|
11801
12517
|
this.context.stdout.write(JSON.stringify(doc) + "\n");
|
|
@@ -11930,7 +12646,7 @@ function rankConfidenceForGrouping(c) {
|
|
|
11930
12646
|
}
|
|
11931
12647
|
|
|
11932
12648
|
// cli/commands/stubs.ts
|
|
11933
|
-
import { Command as
|
|
12649
|
+
import { Command as Command21, Option as Option21 } from "clipanion";
|
|
11934
12650
|
|
|
11935
12651
|
// cli/i18n/stubs.texts.ts
|
|
11936
12652
|
var STUBS_TEXTS = {
|
|
@@ -11945,9 +12661,9 @@ function notImplemented(cmd, verb) {
|
|
|
11945
12661
|
cmd.context.stderr.write(tx(STUBS_TEXTS.notImplemented, { verb }));
|
|
11946
12662
|
return ExitCode.Error;
|
|
11947
12663
|
}
|
|
11948
|
-
var DoctorCommand = class extends
|
|
12664
|
+
var DoctorCommand = class extends Command21 {
|
|
11949
12665
|
static paths = [["doctor"]];
|
|
11950
|
-
static usage =
|
|
12666
|
+
static usage = Command21.Usage({
|
|
11951
12667
|
category: "Setup",
|
|
11952
12668
|
description: planned("Diagnostic report: DB integrity, pending migrations, orphan rows, plugin status, runner availability.")
|
|
11953
12669
|
});
|
|
@@ -11955,23 +12671,23 @@ var DoctorCommand = class extends Command19 {
|
|
|
11955
12671
|
return notImplemented(this, "doctor");
|
|
11956
12672
|
}
|
|
11957
12673
|
};
|
|
11958
|
-
var FindingsCommand = class extends
|
|
12674
|
+
var FindingsCommand = class extends Command21 {
|
|
11959
12675
|
static paths = [["findings"]];
|
|
11960
|
-
static usage =
|
|
12676
|
+
static usage = Command21.Usage({
|
|
11961
12677
|
category: "Browse",
|
|
11962
12678
|
description: planned("Probabilistic findings: injection, stale summaries, low confidence.")
|
|
11963
12679
|
});
|
|
11964
|
-
kind =
|
|
11965
|
-
since =
|
|
11966
|
-
threshold =
|
|
11967
|
-
json =
|
|
12680
|
+
kind = Option21.String("--kind", { required: false });
|
|
12681
|
+
since = Option21.String("--since", { required: false });
|
|
12682
|
+
threshold = Option21.String("--threshold", { required: false });
|
|
12683
|
+
json = Option21.Boolean("--json", false);
|
|
11968
12684
|
async execute() {
|
|
11969
12685
|
return notImplemented(this, "findings");
|
|
11970
12686
|
}
|
|
11971
12687
|
};
|
|
11972
|
-
var ActionsListCommand = class extends
|
|
12688
|
+
var ActionsListCommand = class extends Command21 {
|
|
11973
12689
|
static paths = [["actions", "list"]];
|
|
11974
|
-
static usage =
|
|
12690
|
+
static usage = Command21.Usage({
|
|
11975
12691
|
category: "Jobs",
|
|
11976
12692
|
description: planned("Registered action types (manifest view).")
|
|
11977
12693
|
});
|
|
@@ -11979,138 +12695,125 @@ var ActionsListCommand = class extends Command19 {
|
|
|
11979
12695
|
return notImplemented(this, "actions list");
|
|
11980
12696
|
}
|
|
11981
12697
|
};
|
|
11982
|
-
var ActionsShowCommand = class extends
|
|
12698
|
+
var ActionsShowCommand = class extends Command21 {
|
|
11983
12699
|
static paths = [["actions", "show"]];
|
|
11984
|
-
static usage =
|
|
12700
|
+
static usage = Command21.Usage({
|
|
11985
12701
|
category: "Jobs",
|
|
11986
12702
|
description: planned("Full action manifest, including preconditions and expected duration.")
|
|
11987
12703
|
});
|
|
11988
|
-
id =
|
|
12704
|
+
id = Option21.String({ required: true });
|
|
11989
12705
|
async execute() {
|
|
11990
12706
|
return notImplemented(this, "actions show");
|
|
11991
12707
|
}
|
|
11992
12708
|
};
|
|
11993
|
-
var JobSubmitCommand = class extends
|
|
12709
|
+
var JobSubmitCommand = class extends Command21 {
|
|
11994
12710
|
static paths = [["job", "submit"]];
|
|
11995
|
-
static usage =
|
|
12711
|
+
static usage = Command21.Usage({
|
|
11996
12712
|
category: "Jobs",
|
|
11997
12713
|
description: planned("Enqueue a single job or fan out to every matching node (--all).")
|
|
11998
12714
|
});
|
|
11999
|
-
action =
|
|
12000
|
-
node =
|
|
12001
|
-
all =
|
|
12002
|
-
run =
|
|
12003
|
-
force =
|
|
12004
|
-
ttl =
|
|
12005
|
-
priority =
|
|
12715
|
+
action = Option21.String({ required: true });
|
|
12716
|
+
node = Option21.String("-n", { required: false });
|
|
12717
|
+
all = Option21.Boolean("--all", false);
|
|
12718
|
+
run = Option21.Boolean("--run", false);
|
|
12719
|
+
force = Option21.Boolean("--force", false);
|
|
12720
|
+
ttl = Option21.String("--ttl", { required: false });
|
|
12721
|
+
priority = Option21.String("--priority", { required: false });
|
|
12006
12722
|
async execute() {
|
|
12007
12723
|
return notImplemented(this, "job submit");
|
|
12008
12724
|
}
|
|
12009
12725
|
};
|
|
12010
|
-
var JobListCommand = class extends
|
|
12726
|
+
var JobListCommand = class extends Command21 {
|
|
12011
12727
|
static paths = [["job", "list"]];
|
|
12012
|
-
static usage =
|
|
12013
|
-
status =
|
|
12014
|
-
action =
|
|
12015
|
-
node =
|
|
12728
|
+
static usage = Command21.Usage({ category: "Jobs", description: planned("List jobs.") });
|
|
12729
|
+
status = Option21.String("--status", { required: false });
|
|
12730
|
+
action = Option21.String("--action", { required: false });
|
|
12731
|
+
node = Option21.String("--node", { required: false });
|
|
12016
12732
|
async execute() {
|
|
12017
12733
|
return notImplemented(this, "job list");
|
|
12018
12734
|
}
|
|
12019
12735
|
};
|
|
12020
|
-
var JobShowCommand = class extends
|
|
12736
|
+
var JobShowCommand = class extends Command21 {
|
|
12021
12737
|
static paths = [["job", "show"]];
|
|
12022
|
-
static usage =
|
|
12023
|
-
id =
|
|
12738
|
+
static usage = Command21.Usage({ category: "Jobs", description: planned("Job detail: state, claim time, TTL, runner, content hash.") });
|
|
12739
|
+
id = Option21.String({ required: true });
|
|
12024
12740
|
async execute() {
|
|
12025
12741
|
return notImplemented(this, "job show");
|
|
12026
12742
|
}
|
|
12027
12743
|
};
|
|
12028
|
-
var JobPreviewCommand = class extends
|
|
12744
|
+
var JobPreviewCommand = class extends Command21 {
|
|
12029
12745
|
static paths = [["job", "preview"]];
|
|
12030
|
-
static usage =
|
|
12031
|
-
id =
|
|
12746
|
+
static usage = Command21.Usage({ category: "Jobs", description: planned("Render the job MD file without executing.") });
|
|
12747
|
+
id = Option21.String({ required: true });
|
|
12032
12748
|
async execute() {
|
|
12033
12749
|
return notImplemented(this, "job preview");
|
|
12034
12750
|
}
|
|
12035
12751
|
};
|
|
12036
|
-
var JobClaimCommand = class extends
|
|
12752
|
+
var JobClaimCommand = class extends Command21 {
|
|
12037
12753
|
static paths = [["job", "claim"]];
|
|
12038
|
-
static usage =
|
|
12754
|
+
static usage = Command21.Usage({
|
|
12039
12755
|
category: "Jobs",
|
|
12040
12756
|
description: planned("Atomic primitive: return next queued job id, mark it running.")
|
|
12041
12757
|
});
|
|
12042
|
-
filter =
|
|
12758
|
+
filter = Option21.String("--filter", { required: false });
|
|
12043
12759
|
async execute() {
|
|
12044
12760
|
return notImplemented(this, "job claim");
|
|
12045
12761
|
}
|
|
12046
12762
|
};
|
|
12047
|
-
var JobRunCommand = class extends
|
|
12763
|
+
var JobRunCommand = class extends Command21 {
|
|
12048
12764
|
static paths = [["job", "run"]];
|
|
12049
|
-
static usage =
|
|
12765
|
+
static usage = Command21.Usage({
|
|
12050
12766
|
category: "Jobs",
|
|
12051
12767
|
description: planned("Full CLI-runner loop: claim + spawn + record.")
|
|
12052
12768
|
});
|
|
12053
|
-
all =
|
|
12054
|
-
max =
|
|
12769
|
+
all = Option21.Boolean("--all", false);
|
|
12770
|
+
max = Option21.String("--max", { required: false });
|
|
12055
12771
|
async execute() {
|
|
12056
12772
|
return notImplemented(this, "job run");
|
|
12057
12773
|
}
|
|
12058
12774
|
};
|
|
12059
|
-
var JobStatusCommand = class extends
|
|
12775
|
+
var JobStatusCommand = class extends Command21 {
|
|
12060
12776
|
static paths = [["job", "status"]];
|
|
12061
|
-
static usage =
|
|
12777
|
+
static usage = Command21.Usage({
|
|
12062
12778
|
category: "Jobs",
|
|
12063
12779
|
description: planned("Counts (per status) or single-job status.")
|
|
12064
12780
|
});
|
|
12065
|
-
id =
|
|
12781
|
+
id = Option21.String({ required: false });
|
|
12066
12782
|
async execute() {
|
|
12067
12783
|
return notImplemented(this, "job status");
|
|
12068
12784
|
}
|
|
12069
12785
|
};
|
|
12070
|
-
var JobCancelCommand = class extends
|
|
12786
|
+
var JobCancelCommand = class extends Command21 {
|
|
12071
12787
|
static paths = [["job", "cancel"]];
|
|
12072
|
-
static usage =
|
|
12788
|
+
static usage = Command21.Usage({
|
|
12073
12789
|
category: "Jobs",
|
|
12074
12790
|
description: planned("Force a running job to failed with reason user-cancelled.")
|
|
12075
12791
|
});
|
|
12076
|
-
id =
|
|
12077
|
-
all =
|
|
12792
|
+
id = Option21.String({ required: false });
|
|
12793
|
+
all = Option21.Boolean("--all", false);
|
|
12078
12794
|
async execute() {
|
|
12079
12795
|
return notImplemented(this, "job cancel");
|
|
12080
12796
|
}
|
|
12081
12797
|
};
|
|
12082
|
-
var RecordCommand = class extends
|
|
12798
|
+
var RecordCommand = class extends Command21 {
|
|
12083
12799
|
static paths = [["record"]];
|
|
12084
|
-
static usage =
|
|
12800
|
+
static usage = Command21.Usage({
|
|
12085
12801
|
category: "Jobs",
|
|
12086
12802
|
description: planned("Close a running job with success or failure. Nonce is the sole credential.")
|
|
12087
12803
|
});
|
|
12088
|
-
id =
|
|
12089
|
-
nonce =
|
|
12090
|
-
status =
|
|
12091
|
-
report =
|
|
12092
|
-
tokensIn =
|
|
12093
|
-
tokensOut =
|
|
12094
|
-
durationMs =
|
|
12095
|
-
model =
|
|
12096
|
-
error =
|
|
12804
|
+
id = Option21.String("--id", { required: true });
|
|
12805
|
+
nonce = Option21.String("--nonce", { required: true });
|
|
12806
|
+
status = Option21.String("--status", { required: true });
|
|
12807
|
+
report = Option21.String("--report", { required: false });
|
|
12808
|
+
tokensIn = Option21.String("--tokens-in", { required: false });
|
|
12809
|
+
tokensOut = Option21.String("--tokens-out", { required: false });
|
|
12810
|
+
durationMs = Option21.String("--duration-ms", { required: false });
|
|
12811
|
+
model = Option21.String("--model", { required: false });
|
|
12812
|
+
error = Option21.String("--error", { required: false });
|
|
12097
12813
|
async execute() {
|
|
12098
12814
|
return notImplemented(this, "record");
|
|
12099
12815
|
}
|
|
12100
12816
|
};
|
|
12101
|
-
var ServeCommand = class extends Command19 {
|
|
12102
|
-
static paths = [["serve"]];
|
|
12103
|
-
static usage = Command19.Usage({
|
|
12104
|
-
category: "Setup",
|
|
12105
|
-
description: planned("Start Hono + WebSocket for the Web UI. Single-port mandate: SPA + REST + WS under one listener.")
|
|
12106
|
-
});
|
|
12107
|
-
port = Option19.String("--port", { required: false });
|
|
12108
|
-
host = Option19.String("--host", { required: false });
|
|
12109
|
-
noOpen = Option19.Boolean("--no-open", false);
|
|
12110
|
-
async execute() {
|
|
12111
|
-
return notImplemented(this, "serve");
|
|
12112
|
-
}
|
|
12113
|
-
};
|
|
12114
12817
|
var STUB_COMMANDS = [
|
|
12115
12818
|
DoctorCommand,
|
|
12116
12819
|
FindingsCommand,
|
|
@@ -12124,12 +12827,11 @@ var STUB_COMMANDS = [
|
|
|
12124
12827
|
JobRunCommand,
|
|
12125
12828
|
JobStatusCommand,
|
|
12126
12829
|
JobCancelCommand,
|
|
12127
|
-
RecordCommand
|
|
12128
|
-
ServeCommand
|
|
12830
|
+
RecordCommand
|
|
12129
12831
|
];
|
|
12130
12832
|
|
|
12131
12833
|
// cli/commands/version.ts
|
|
12132
|
-
import { Command as
|
|
12834
|
+
import { Command as Command22 } from "clipanion";
|
|
12133
12835
|
|
|
12134
12836
|
// cli/i18n/version.texts.ts
|
|
12135
12837
|
var VERSION_TEXTS = {
|
|
@@ -12139,17 +12841,19 @@ var VERSION_TEXTS = {
|
|
|
12139
12841
|
};
|
|
12140
12842
|
|
|
12141
12843
|
// cli/commands/version.ts
|
|
12142
|
-
var VersionCommand = class extends
|
|
12844
|
+
var VersionCommand = class extends SmCommand {
|
|
12143
12845
|
static paths = [["version"]];
|
|
12144
|
-
static usage =
|
|
12846
|
+
static usage = Command22.Usage({
|
|
12145
12847
|
category: "Introspection",
|
|
12146
12848
|
description: "Print the CLI / kernel / spec / runtime / db-schema version matrix."
|
|
12147
12849
|
});
|
|
12148
|
-
|
|
12149
|
-
|
|
12850
|
+
// Informational verb — no `done in <…>` line; the version matrix is
|
|
12851
|
+
// the entire output.
|
|
12852
|
+
emitElapsed = false;
|
|
12853
|
+
async run() {
|
|
12150
12854
|
const runtime = `Node ${process.version}`;
|
|
12151
12855
|
const kernelVersion = VERSION;
|
|
12152
|
-
const specVersion = await
|
|
12856
|
+
const specVersion = await resolveSpecVersion3();
|
|
12153
12857
|
const dbSchema = await resolveDbSchemaVersion();
|
|
12154
12858
|
if (this.json) {
|
|
12155
12859
|
const payload = {
|
|
@@ -12175,7 +12879,7 @@ var VersionCommand = class extends Command20 {
|
|
|
12175
12879
|
return ExitCode.Ok;
|
|
12176
12880
|
}
|
|
12177
12881
|
};
|
|
12178
|
-
async function
|
|
12882
|
+
async function resolveSpecVersion3() {
|
|
12179
12883
|
try {
|
|
12180
12884
|
const mod = await import("@skill-map/spec", { with: { type: "json" } });
|
|
12181
12885
|
const version = mod.default?.specPackageVersion;
|
|
@@ -12211,6 +12915,7 @@ cli.register(HelpCommand);
|
|
|
12211
12915
|
cli.register(InitCommand);
|
|
12212
12916
|
cli.register(ScanCommand);
|
|
12213
12917
|
cli.register(ScanCompareCommand);
|
|
12918
|
+
cli.register(ServeCommand);
|
|
12214
12919
|
cli.register(WatchCommand);
|
|
12215
12920
|
cli.register(VersionCommand);
|
|
12216
12921
|
cli.register(ListCommand);
|