@okfit/cli 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/commands/graph.js +75 -0
- package/commands/init.js +1 -1
- package/commands/lint.js +95 -0
- package/commands/root.js +14 -8
- package/commands/stale.js +76 -0
- package/commands/validate.js +2 -1
- package/commands/verify.js +1 -1
- package/index.d.ts +7 -7
- package/package.json +4 -4
- package/render/stale.js +18 -0
- package/version.js +1 -1
package/README.md
CHANGED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { setExitCode } from "../internal/exit.js";
|
|
2
|
+
import { CLI_VERSION } from "../version.js";
|
|
3
|
+
import { Argument, Command, Flag } from "effect/unstable/cli";
|
|
4
|
+
import { GraphEnvelope, graphEnvelope, jsonError, provideConfig, resolveProjectConfig, runGraph } from "@okfit/engine";
|
|
5
|
+
import { Console, Effect, Option, Schema } from "effect";
|
|
6
|
+
import { OKF_SPEC_VERSION } from "@okfit/core";
|
|
7
|
+
|
|
8
|
+
//#region src/commands/graph.ts
|
|
9
|
+
/** `[path]` is the PROJECT root (K-2), never the bundle root. Absolute at parse time (K-50). */
|
|
10
|
+
const pathArg = Argument.Path("path", { pathType: "directory" }).pipe(Argument.optional, Argument.withDescription("project root to start config discovery from (default: current directory); never the bundle root"));
|
|
11
|
+
/** K-1: no `mustExist` — the handler stats the path itself, before building any layer. */
|
|
12
|
+
const configFlag = Flag.File("config").pipe(Flag.optional, Flag.withDescription("explicit config file; skips discovery"));
|
|
13
|
+
/** Default `mermaid`, unlike every other command's `--format` (which defaults `human`). */
|
|
14
|
+
const formatFlag = Flag.Literals("format", [
|
|
15
|
+
"mermaid",
|
|
16
|
+
"dot",
|
|
17
|
+
"json"
|
|
18
|
+
]).pipe(Flag.withDefault("mermaid"), Flag.withDescription("output format: mermaid (default), dot, or json"));
|
|
19
|
+
/**
|
|
20
|
+
* `okfit graph [path] [--config <file>] [--format mermaid|dot|json]`.
|
|
21
|
+
*
|
|
22
|
+
* Same handler skeleton as `commands/validate.ts`/`commands/context.ts` —
|
|
23
|
+
* stat `--config` (K-1) via `provideConfig`, resolve the project and
|
|
24
|
+
* bundle roots through `resolveProjectConfig` — then it diverges:
|
|
25
|
+
* `@okfit/engine`'s `runGraph` (`Bundle.load` then `Graph.fromBundle`),
|
|
26
|
+
* render, and always exit `0`. `mermaid`/`dot` print the graph's own
|
|
27
|
+
* `toMermaid()`/`toGraphViz()` text raw to stdout — nothing else on
|
|
28
|
+
* stdout, no summary line — since either is meant to be piped straight
|
|
29
|
+
* into a renderer. Only `--format json` failures get the K-22 `jsonError`
|
|
30
|
+
* envelope; a `mermaid`/`dot` failure renders the usual way through
|
|
31
|
+
* `bin.ts`'s `CliRuntime.reportFailures` and carries no stdout envelope.
|
|
32
|
+
*
|
|
33
|
+
* @public
|
|
34
|
+
*/
|
|
35
|
+
const graphCommand = Command.make("graph", {
|
|
36
|
+
path: pathArg,
|
|
37
|
+
config: configFlag,
|
|
38
|
+
format: formatFlag
|
|
39
|
+
}, (input) => Effect.gen(function* () {
|
|
40
|
+
const cwd = process.cwd();
|
|
41
|
+
const discoveryCwd = Option.getOrElse(input.path, () => cwd);
|
|
42
|
+
const body = Effect.gen(function* () {
|
|
43
|
+
const { bundleRoot, config: merged, profile } = yield* resolveProjectConfig({
|
|
44
|
+
pathArg: input.path,
|
|
45
|
+
explicitConfigPath: input.config,
|
|
46
|
+
cwd
|
|
47
|
+
});
|
|
48
|
+
const result = yield* runGraph({ root: bundleRoot });
|
|
49
|
+
if (input.format === "json") {
|
|
50
|
+
const envelope = graphEnvelope({
|
|
51
|
+
okfitVersion: CLI_VERSION,
|
|
52
|
+
producer: "okfit",
|
|
53
|
+
okfVersion: merged.okf_version ?? OKF_SPEC_VERSION,
|
|
54
|
+
root: bundleRoot,
|
|
55
|
+
profile: Option.match(profile, {
|
|
56
|
+
onNone: () => null,
|
|
57
|
+
onSome: (p) => p.name
|
|
58
|
+
}),
|
|
59
|
+
nodes: result.graph.nodes,
|
|
60
|
+
edges: result.graph.edges
|
|
61
|
+
});
|
|
62
|
+
yield* Console.log(JSON.stringify(Schema.encodeSync(GraphEnvelope)(envelope)));
|
|
63
|
+
} else if (input.format === "dot") yield* Console.log(result.graph.toGraphViz());
|
|
64
|
+
else yield* Console.log(result.graph.toMermaid());
|
|
65
|
+
setExitCode(0);
|
|
66
|
+
}).pipe(provideConfig({
|
|
67
|
+
explicitConfigPath: input.config,
|
|
68
|
+
discoveryCwd
|
|
69
|
+
}));
|
|
70
|
+
if (input.format === "json") return yield* body.pipe(Effect.tapError((error) => Console.log(JSON.stringify(jsonError(error, CLI_VERSION)))));
|
|
71
|
+
return yield* body;
|
|
72
|
+
})).pipe(Command.withDescription("Render the bundle's link graph: frontmatter path fields and body links, as Mermaid, DOT, or JSON."));
|
|
73
|
+
|
|
74
|
+
//#endregion
|
|
75
|
+
export { graphCommand };
|
package/commands/init.js
CHANGED
|
@@ -4,8 +4,8 @@ import { displayRoot, human, summary } from "../render/human.js";
|
|
|
4
4
|
import { Argument, Command, Flag } from "effect/unstable/cli";
|
|
5
5
|
import { CONFIG_RELATIVE_PATH, InitOverwriteError, Now, SCHEMA_DIRECTIVE, collect, configValue, files, forDiagnostics, provideConfig, resolveBundleRoot, resolveProjectRoot, run, targetPaths } from "@okfit/engine";
|
|
6
6
|
import { Console, DateTime, Effect, FileSystem, Layer, Option, Path } from "effect";
|
|
7
|
-
import { Git } from "@effected/git";
|
|
8
7
|
import { OKF_SPEC_VERSION, OkfitConfig, OkfitConfigFile } from "@okfit/core";
|
|
8
|
+
import { Git } from "@effected/git";
|
|
9
9
|
import { GitHistory, Profiles } from "@okfit/profiles";
|
|
10
10
|
|
|
11
11
|
//#region src/commands/init.ts
|
package/commands/lint.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { setExitCode } from "../internal/exit.js";
|
|
2
|
+
import { CLI_VERSION } from "../version.js";
|
|
3
|
+
import { useColor } from "../internal/tty.js";
|
|
4
|
+
import { displayRoot, human, summary } from "../render/human.js";
|
|
5
|
+
import { Argument, Command, Flag } from "effect/unstable/cli";
|
|
6
|
+
import { JsonEnvelope, Now, collect, forDiagnostics, json, jsonError, provideConfig, resolveProjectConfig, run } from "@okfit/engine";
|
|
7
|
+
import { Console, Effect, Layer, Option, Path, Schema } from "effect";
|
|
8
|
+
import { OKF_SPEC_VERSION } from "@okfit/core";
|
|
9
|
+
import { Git } from "@effected/git";
|
|
10
|
+
import { GitHistory } from "@okfit/profiles";
|
|
11
|
+
|
|
12
|
+
//#region src/commands/lint.ts
|
|
13
|
+
/** `[path]` is the PROJECT root (K-2), never the bundle root. Absolute at parse time (K-50). */
|
|
14
|
+
const pathArg = Argument.Path("path", { pathType: "directory" }).pipe(Argument.optional, Argument.withDescription("project root to start config discovery from (default: current directory); never the bundle root"));
|
|
15
|
+
/** K-1: no `mustExist` — the handler stats the path itself, before building any layer. */
|
|
16
|
+
const configFlag = Flag.File("config").pipe(Flag.optional, Flag.withDescription("explicit config file; skips discovery"));
|
|
17
|
+
const formatFlag = Flag.Literals("format", ["human", "json"]).pipe(Flag.withDefault("human"), Flag.withDescription("output format: human (default) or json"));
|
|
18
|
+
/** S-31: skips `Provenance.lint`'s git tier for this invocation, without touching `[lint]`. */
|
|
19
|
+
const skipProvenanceFlag = Flag.Boolean("skip-provenance").pipe(Flag.withDefault(false), Flag.withDescription("skip the generated-at-drift lint's git tier for this run"));
|
|
20
|
+
/**
|
|
21
|
+
* `okfit lint [path] [--config <file>] [--format human|json] [--skip-provenance]`.
|
|
22
|
+
*
|
|
23
|
+
* Same handler skeleton as `commands/validate.ts` — stat `--config` (K-1)
|
|
24
|
+
* via `provideConfig`, discover, resolve the profile and roots through
|
|
25
|
+
* `resolveProjectConfig`, run the engine's `validate/run.ts#run` (both
|
|
26
|
+
* conformance AND lint tiers still run — core's `Validate.all` does not
|
|
27
|
+
* separate them), render, and set the exit code — except the CONFORMANCE
|
|
28
|
+
* tier is dropped from what this command collects and reports:
|
|
29
|
+
* `collect([], result.report.lint, result.profileDiagnostics)`, never
|
|
30
|
+
* `result.report.conformance`. `conformance_errors` in the JSON envelope's
|
|
31
|
+
* `summary` is therefore always `0`.
|
|
32
|
+
*
|
|
33
|
+
* @public
|
|
34
|
+
*/
|
|
35
|
+
const lintCommand = Command.make("lint", {
|
|
36
|
+
path: pathArg,
|
|
37
|
+
config: configFlag,
|
|
38
|
+
format: formatFlag,
|
|
39
|
+
skipProvenance: skipProvenanceFlag
|
|
40
|
+
}, (input) => Effect.gen(function* () {
|
|
41
|
+
const cwd = process.cwd();
|
|
42
|
+
const discoveryCwd = Option.getOrElse(input.path, () => cwd);
|
|
43
|
+
const now = yield* Now;
|
|
44
|
+
const path = yield* Path.Path;
|
|
45
|
+
const body = Effect.gen(function* () {
|
|
46
|
+
const { bundleRoot, config: merged, profile } = yield* resolveProjectConfig({
|
|
47
|
+
pathArg: input.path,
|
|
48
|
+
explicitConfigPath: input.config,
|
|
49
|
+
cwd
|
|
50
|
+
});
|
|
51
|
+
const result = yield* run({
|
|
52
|
+
root: bundleRoot,
|
|
53
|
+
config: merged,
|
|
54
|
+
profile,
|
|
55
|
+
now,
|
|
56
|
+
skipProvenance: input.skipProvenance
|
|
57
|
+
}).pipe(Effect.provide(Layer.mergeAll(Git.layer, GitHistory.layer)));
|
|
58
|
+
const diagnostics = collect([], result.report.lint, result.profileDiagnostics);
|
|
59
|
+
const code = forDiagnostics(diagnostics);
|
|
60
|
+
if (input.format === "json") {
|
|
61
|
+
const envelope = json({
|
|
62
|
+
okfitVersion: CLI_VERSION,
|
|
63
|
+
producer: "okfit",
|
|
64
|
+
okfVersion: merged.okf_version ?? OKF_SPEC_VERSION,
|
|
65
|
+
root: bundleRoot,
|
|
66
|
+
profile: Option.match(profile, {
|
|
67
|
+
onNone: () => null,
|
|
68
|
+
onSome: (p) => p.name
|
|
69
|
+
}),
|
|
70
|
+
exitCode: code,
|
|
71
|
+
concepts: result.bundle.concepts.size,
|
|
72
|
+
diagnostics
|
|
73
|
+
});
|
|
74
|
+
yield* Console.log(JSON.stringify(Schema.encodeSync(JsonEnvelope)(envelope)));
|
|
75
|
+
} else {
|
|
76
|
+
for (const diagnosticLine of human(diagnostics, { color: useColor() })) yield* Console.log(diagnosticLine);
|
|
77
|
+
const counts = {
|
|
78
|
+
errors: diagnostics.filter((d) => d.severity === "error").length,
|
|
79
|
+
warnings: diagnostics.filter((d) => d.severity === "warning").length,
|
|
80
|
+
info: diagnostics.filter((d) => d.severity === "info").length,
|
|
81
|
+
concepts: result.bundle.concepts.size
|
|
82
|
+
};
|
|
83
|
+
yield* Console.error(summary(counts, displayRoot(cwd, bundleRoot, path)));
|
|
84
|
+
}
|
|
85
|
+
setExitCode(code);
|
|
86
|
+
}).pipe(provideConfig({
|
|
87
|
+
explicitConfigPath: input.config,
|
|
88
|
+
discoveryCwd
|
|
89
|
+
}));
|
|
90
|
+
if (input.format === "json") return yield* body.pipe(Effect.tapError((error) => Console.log(JSON.stringify(jsonError(error, CLI_VERSION)))));
|
|
91
|
+
return yield* body;
|
|
92
|
+
})).pipe(Command.withDescription("Load config and bundle, run lint checks (and the profile check), and render the diagnostics; never conformance."));
|
|
93
|
+
|
|
94
|
+
//#endregion
|
|
95
|
+
export { lintCommand };
|
package/commands/root.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { contextCommand } from "./context.js";
|
|
2
|
+
import { graphCommand } from "./graph.js";
|
|
2
3
|
import { initCommand } from "./init.js";
|
|
4
|
+
import { lintCommand } from "./lint.js";
|
|
5
|
+
import { staleCommand } from "./stale.js";
|
|
3
6
|
import { syncCommand } from "./sync.js";
|
|
4
7
|
import { validateCommand } from "./validate.js";
|
|
5
8
|
import { verifyCommand } from "./verify.js";
|
|
@@ -14,13 +17,13 @@ import { Command } from "effect/unstable/cli";
|
|
|
14
17
|
* `okfit` therefore prints the root help and exits `0` with no code in this
|
|
15
18
|
* package at all.
|
|
16
19
|
*
|
|
17
|
-
* `validate`, `init`, `context`, `verify`,
|
|
18
|
-
* nothing else is registered here.
|
|
19
|
-
*
|
|
20
|
-
* (contract §4.2). The
|
|
21
|
-
* `context` nor `verify`
|
|
22
|
-
*
|
|
23
|
-
* (contract §4.2).
|
|
20
|
+
* `validate`, `init`, `context`, `verify`, `sync`, `lint`, `graph`, and
|
|
21
|
+
* `stale` are the whole command tree; nothing else is registered here.
|
|
22
|
+
* Each is appended in introduction order, never reordered in, so
|
|
23
|
+
* `--help`'s subcommand list reads that way too (contract §4.2). The
|
|
24
|
+
* top-level description is left unchanged: neither `context` nor `verify`
|
|
25
|
+
* validates or scaffolds, but widening the sentence for the orientation-
|
|
26
|
+
* and attestation-only commands buys nothing (contract §4.2).
|
|
24
27
|
*
|
|
25
28
|
* @public
|
|
26
29
|
*/
|
|
@@ -29,7 +32,10 @@ const rootCommand = Command.make("okfit", {}).pipe(Command.withDescription("Open
|
|
|
29
32
|
initCommand,
|
|
30
33
|
contextCommand,
|
|
31
34
|
verifyCommand,
|
|
32
|
-
syncCommand
|
|
35
|
+
syncCommand,
|
|
36
|
+
lintCommand,
|
|
37
|
+
graphCommand,
|
|
38
|
+
staleCommand
|
|
33
39
|
]));
|
|
34
40
|
|
|
35
41
|
//#endregion
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { setExitCode } from "../internal/exit.js";
|
|
2
|
+
import { CLI_VERSION } from "../version.js";
|
|
3
|
+
import { displayRoot } from "../render/human.js";
|
|
4
|
+
import { humanStale, staleSummary } from "../render/stale.js";
|
|
5
|
+
import { Argument, Command, Flag } from "effect/unstable/cli";
|
|
6
|
+
import { Now, StaleEnvelope, jsonError, provideConfig, resolveProjectConfig, runStale, staleEnvelope } from "@okfit/engine";
|
|
7
|
+
import { Console, Effect, Option, Path, Schema } from "effect";
|
|
8
|
+
import { OKF_SPEC_VERSION } from "@okfit/core";
|
|
9
|
+
|
|
10
|
+
//#region src/commands/stale.ts
|
|
11
|
+
/** `[path]` is the PROJECT root (K-2), never the bundle root. Absolute at parse time (K-50). */
|
|
12
|
+
const pathArg = Argument.Path("path", { pathType: "directory" }).pipe(Argument.optional, Argument.withDescription("project root to start config discovery from (default: current directory); never the bundle root"));
|
|
13
|
+
/** K-1: no `mustExist` — the handler stats the path itself, before building any layer. */
|
|
14
|
+
const configFlag = Flag.File("config").pipe(Flag.optional, Flag.withDescription("explicit config file; skips discovery"));
|
|
15
|
+
const formatFlag = Flag.Literals("format", ["human", "json"]).pipe(Flag.withDefault("human"), Flag.withDescription("output format: human (default) or json"));
|
|
16
|
+
/**
|
|
17
|
+
* `okfit stale [path] [--config <file>] [--format human|json]`.
|
|
18
|
+
*
|
|
19
|
+
* Same handler skeleton as `commands/validate.ts`/`commands/context.ts` —
|
|
20
|
+
* stat `--config` (K-1) via `provideConfig`, resolve the project and
|
|
21
|
+
* bundle roots through `resolveProjectConfig` — then it diverges:
|
|
22
|
+
* `@okfit/engine`'s `runStale` (`Bundle.load` then
|
|
23
|
+
* `Derive.staleReport(bundle, now)`), render, and always exit `0`: this is
|
|
24
|
+
* a report, not a check, and the `stale` lint rule (part of `okfit
|
|
25
|
+
* validate`/`okfit lint`) is where staleness fails a run.
|
|
26
|
+
*
|
|
27
|
+
* @public
|
|
28
|
+
*/
|
|
29
|
+
const staleCommand = Command.make("stale", {
|
|
30
|
+
path: pathArg,
|
|
31
|
+
config: configFlag,
|
|
32
|
+
format: formatFlag
|
|
33
|
+
}, (input) => Effect.gen(function* () {
|
|
34
|
+
const cwd = process.cwd();
|
|
35
|
+
const discoveryCwd = Option.getOrElse(input.path, () => cwd);
|
|
36
|
+
const now = yield* Now;
|
|
37
|
+
const path = yield* Path.Path;
|
|
38
|
+
const body = Effect.gen(function* () {
|
|
39
|
+
const { bundleRoot, config: merged, profile } = yield* resolveProjectConfig({
|
|
40
|
+
pathArg: input.path,
|
|
41
|
+
explicitConfigPath: input.config,
|
|
42
|
+
cwd
|
|
43
|
+
});
|
|
44
|
+
const result = yield* runStale({
|
|
45
|
+
root: bundleRoot,
|
|
46
|
+
now
|
|
47
|
+
});
|
|
48
|
+
const envelope = staleEnvelope({
|
|
49
|
+
okfitVersion: CLI_VERSION,
|
|
50
|
+
producer: "okfit",
|
|
51
|
+
okfVersion: merged.okf_version ?? OKF_SPEC_VERSION,
|
|
52
|
+
root: bundleRoot,
|
|
53
|
+
profile: Option.match(profile, {
|
|
54
|
+
onNone: () => null,
|
|
55
|
+
onSome: (p) => p.name
|
|
56
|
+
}),
|
|
57
|
+
now,
|
|
58
|
+
concepts: result.bundle.concepts.size,
|
|
59
|
+
items: result.items
|
|
60
|
+
});
|
|
61
|
+
if (input.format === "json") yield* Console.log(JSON.stringify(Schema.encodeSync(StaleEnvelope)(envelope)));
|
|
62
|
+
else {
|
|
63
|
+
for (const line of humanStale(envelope.items)) yield* Console.log(line);
|
|
64
|
+
yield* Console.error(staleSummary(envelope.summary.stale, envelope.summary.concepts, displayRoot(cwd, bundleRoot, path)));
|
|
65
|
+
}
|
|
66
|
+
setExitCode(0);
|
|
67
|
+
}).pipe(provideConfig({
|
|
68
|
+
explicitConfigPath: input.config,
|
|
69
|
+
discoveryCwd
|
|
70
|
+
}));
|
|
71
|
+
if (input.format === "json") return yield* body.pipe(Effect.tapError((error) => Console.log(JSON.stringify(jsonError(error, CLI_VERSION)))));
|
|
72
|
+
return yield* body;
|
|
73
|
+
})).pipe(Command.withDescription("List every concept whose stale_after instant has passed as of now, with how many whole days past it."));
|
|
74
|
+
|
|
75
|
+
//#endregion
|
|
76
|
+
export { staleCommand };
|
package/commands/validate.js
CHANGED
|
@@ -5,8 +5,8 @@ import { displayRoot, human, summary } from "../render/human.js";
|
|
|
5
5
|
import { Argument, Command, Flag } from "effect/unstable/cli";
|
|
6
6
|
import { JsonEnvelope, Now, collect, forDiagnostics, json, jsonError, provideConfig, resolveProjectConfig, run } from "@okfit/engine";
|
|
7
7
|
import { Console, Effect, Layer, Option, Path, Schema } from "effect";
|
|
8
|
-
import { Git } from "@effected/git";
|
|
9
8
|
import { OKF_SPEC_VERSION } from "@okfit/core";
|
|
9
|
+
import { Git } from "@effected/git";
|
|
10
10
|
import { GitHistory } from "@okfit/profiles";
|
|
11
11
|
|
|
12
12
|
//#region src/commands/validate.ts
|
|
@@ -59,6 +59,7 @@ const validateCommand = Command.make("validate", {
|
|
|
59
59
|
if (input.format === "json") {
|
|
60
60
|
const envelope = json({
|
|
61
61
|
okfitVersion: CLI_VERSION,
|
|
62
|
+
producer: "okfit",
|
|
62
63
|
okfVersion: merged.okf_version ?? OKF_SPEC_VERSION,
|
|
63
64
|
root: bundleRoot,
|
|
64
65
|
profile: Option.match(profile, {
|
package/commands/verify.js
CHANGED
|
@@ -5,8 +5,8 @@ import { humanVerify } from "../render/verify.js";
|
|
|
5
5
|
import { Argument, Command, Flag } from "effect/unstable/cli";
|
|
6
6
|
import { Now, VerifyEnvelope, jsonError, provideConfig, resolveProjectConfig, runVerify, verifyEnvelope } from "@okfit/engine";
|
|
7
7
|
import { Console, DateTime, Effect, Option, Path, Schema } from "effect";
|
|
8
|
-
import { Git } from "@effected/git";
|
|
9
8
|
import { Timestamp } from "@okfit/core";
|
|
9
|
+
import { Git } from "@effected/git";
|
|
10
10
|
|
|
11
11
|
//#region src/commands/verify.ts
|
|
12
12
|
/** V-6: tolerant id, normalised through `ConceptId.normalize`; never `Argument.Path`. */
|
package/index.d.ts
CHANGED
|
@@ -10,13 +10,13 @@ import "effect";
|
|
|
10
10
|
* `okfit` therefore prints the root help and exits `0` with no code in this
|
|
11
11
|
* package at all.
|
|
12
12
|
*
|
|
13
|
-
* `validate`, `init`, `context`, `verify`,
|
|
14
|
-
* nothing else is registered here.
|
|
15
|
-
*
|
|
16
|
-
* (contract §4.2). The
|
|
17
|
-
* `context` nor `verify`
|
|
18
|
-
*
|
|
19
|
-
* (contract §4.2).
|
|
13
|
+
* `validate`, `init`, `context`, `verify`, `sync`, `lint`, `graph`, and
|
|
14
|
+
* `stale` are the whole command tree; nothing else is registered here.
|
|
15
|
+
* Each is appended in introduction order, never reordered in, so
|
|
16
|
+
* `--help`'s subcommand list reads that way too (contract §4.2). The
|
|
17
|
+
* top-level description is left unchanged: neither `context` nor `verify`
|
|
18
|
+
* validates or scaffolds, but widening the sentence for the orientation-
|
|
19
|
+
* and attestation-only commands buys nothing (contract §4.2).
|
|
20
20
|
*
|
|
21
21
|
* @public
|
|
22
22
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@okfit/cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The okfit command line: validate, lint, index, and inspect Open Knowledge Format (OKF) bundles.",
|
|
6
6
|
"keywords": [
|
|
@@ -52,9 +52,9 @@
|
|
|
52
52
|
"@effected/toml": "^0.7.0",
|
|
53
53
|
"@effected/walker": "^0.9.0",
|
|
54
54
|
"@effected/yaml": "^0.15.1",
|
|
55
|
-
"@okfit/core": "0.4.
|
|
56
|
-
"@okfit/engine": "0.
|
|
57
|
-
"@okfit/profiles": "0.
|
|
55
|
+
"@okfit/core": "0.4.1",
|
|
56
|
+
"@okfit/engine": "0.4.1",
|
|
57
|
+
"@okfit/profiles": "0.6.0",
|
|
58
58
|
"effect": "4.0.0-rc.115"
|
|
59
59
|
},
|
|
60
60
|
"engines": {
|
package/render/stale.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/render/stale.ts
|
|
2
|
+
/**
|
|
3
|
+
* One line per stale concept: `<id> <stale_after ISO> (<N> days past)`.
|
|
4
|
+
* `items` is `StaleEnvelope`'s own `items` array — already sorted by id
|
|
5
|
+
* (`Derive.staleReport`) — so this renderer never re-sorts.
|
|
6
|
+
*
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
const humanStale = (items) => items.map((item) => `${item.id} ${item.stale_after} (${item.days_past} days past)`);
|
|
10
|
+
/**
|
|
11
|
+
* `<N> stale concepts of <M> in <root>` — the one-line stderr summary.
|
|
12
|
+
*
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
const staleSummary = (stale, concepts, root) => `${stale} stale concepts of ${concepts} in ${root}`;
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
export { humanStale, staleSummary };
|