@okfit/cli 0.4.1 → 0.5.1

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 CHANGED
@@ -113,6 +113,34 @@ $ echo $?
113
113
  and names every colliding file in one error. An ancestor directory's config
114
114
  is a legitimate discovery hit, not a collision, and is never probed.
115
115
 
116
+ If the repository lints markdown, the bundle needs two exemptions from
117
+ markdownlint's defaults, and neither can be satisfied at the source: MD025
118
+ (one top-level heading) counts a concept's frontmatter `title:` as its H1,
119
+ so the body H1 the spec requires reads as a second one, and the spec shapes
120
+ every `index.md` as `# Section` groups, one H1 per concept type. `log.md`
121
+ is fine: `init` and `sync` both start it with `# Log`. Add this to a
122
+ `.markdownlint-cli2.jsonc` at the repository root, adjusting the bundle
123
+ path if `bundle.path` is not `okf`:
124
+
125
+ ```jsonc
126
+ "overrides": [
127
+ {
128
+ "combine": "merge",
129
+ "config": { "MD025": { "front_matter_title": "" } },
130
+ "filter": ["okf/**/*.md", "!okf/**/index.md"]
131
+ },
132
+ {
133
+ "combine": "merge",
134
+ "config": { "MD025": false },
135
+ "filter": ["okf/**/index.md"]
136
+ }
137
+ ]
138
+ ```
139
+
140
+ A repository whose lint config cannot express overrides can drop a nested
141
+ `okf/.markdownlint-cli2.jsonc` carrying `{ "config": { "MD025": false } }`
142
+ instead; the bundle loader ignores non-markdown files.
143
+
116
144
  `--profile <name>` picks the profile `init` scaffolds for (default: the
117
145
  config's `bundle.profile`, itself defaulting to `software-project`); an
118
146
  unrecognised name is a warning, not a failure — `init` continues with the
@@ -250,6 +278,7 @@ nothing else (no summary line, no warnings — those still go to stderr):
250
278
  {
251
279
  "schema": 1,
252
280
  "okfit_version": "0.1.0",
281
+ "producer": "okfit",
253
282
  "okf_version": "0.2",
254
283
  "root": "/abs/path/to/my-repo/okf",
255
284
  "profile": "software-project",
@@ -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
@@ -3,10 +3,9 @@ import { useColor } from "../internal/tty.js";
3
3
  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
- import { Console, DateTime, Effect, FileSystem, Layer, Option, Path, Schema } from "effect";
7
- import { TomlCodec } from "@effected/config-file";
8
- import { Git } from "@effected/git";
6
+ import { Console, DateTime, Effect, FileSystem, Layer, Option, Path } from "effect";
9
7
  import { OKF_SPEC_VERSION, OkfitConfig, OkfitConfigFile } from "@okfit/core";
8
+ import { Git } from "@effected/git";
10
9
  import { GitHistory, Profiles } from "@okfit/profiles";
11
10
 
12
11
  //#region src/commands/init.ts
@@ -100,7 +99,8 @@ const initCommand = Command.make("init", {
100
99
  explicitConfigPath: input.config,
101
100
  discoveryCwd
102
101
  })(Effect.gen(function* () {
103
- const winner = (yield* (yield* OkfitConfigFile).discover)[0];
102
+ const configFile = yield* OkfitConfigFile;
103
+ const winner = (yield* configFile.discover)[0];
104
104
  const fileConfig = winner === void 0 ? { extensions: {} } : winner.value;
105
105
  const profileName = Option.getOrElse(input.profile, () => fileConfig.bundle?.profile ?? DEFAULT_PROFILE_NAME);
106
106
  const profile = Profiles.get(profileName);
@@ -145,12 +145,10 @@ const initCommand = Command.make("init", {
145
145
  });
146
146
  for (const target of paths) yield* fs.makeDirectory(path.dirname(target), { recursive: true });
147
147
  const bundlePath = merged.bundle?.path ?? DEFAULT_BUNDLE_PATH;
148
- const encoded = yield* Schema.encodeEffect(OkfitConfig)(configValue({
148
+ yield* configFile.write(configValue({
149
149
  ...scaffoldOptions,
150
150
  bundlePath
151
- }));
152
- const toml = yield* TomlCodec.stringify(encoded);
153
- yield* fs.writeFileString(`${projectRoot}/${CONFIG_RELATIVE_PATH}`, `${SCHEMA_DIRECTIVE}${toml}`);
151
+ }), `${projectRoot}/${CONFIG_RELATIVE_PATH}`, { header: SCHEMA_DIRECTIVE });
154
152
  const scaffoldFiles = yield* files(scaffoldOptions);
155
153
  for (const file of scaffoldFiles) yield* fs.writeFileString(file.path, file.contents);
156
154
  yield* Console.log(`Initialized ${displayRoot(cwd, bundleRoot, path)} with the ${profileName} profile`);
@@ -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`, and `sync` are the whole command tree;
18
- * nothing else is registered here. Each is appended in introduction order,
19
- * never reordered in, so `--help`'s subcommand list reads that way too
20
- * (contract §4.2). The top-level description is left unchanged: neither
21
- * `context` nor `verify` validates or scaffolds, but widening the sentence
22
- * for the orientation- and attestation-only commands buys nothing
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 };
@@ -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, {
@@ -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,17 +10,17 @@ 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`, and `sync` are the whole command tree;
14
- * nothing else is registered here. Each is appended in introduction order,
15
- * never reordered in, so `--help`'s subcommand list reads that way too
16
- * (contract §4.2). The top-level description is left unchanged: neither
17
- * `context` nor `verify` validates or scaffolds, but widening the sentence
18
- * for the orientation- and attestation-only commands buys nothing
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
  */
23
- export declare const rootCommand: Command.Command<"okfit", {} | {}, {}, import("@okfit/profiles").AgentActorUnconfiguredError | import("@okfit/core").BundleDepthExceededError | import("@okfit/core").BundleReadError | import("@okfit/core").BundleRootNotFoundError | import("@effected/config-file").ConfigCodecError | import("@effected/config-file").ConfigFileReadError | import("@okfit/engine").ConfigMalformedError | import("@okfit/engine").ConfigPathNotFoundError | import("@effected/config-file").ConfigValidationError | import("@effected/markdown").FrontmatterEncodeError | import("@effected/markdown").FrontmatterFormatMismatchError | import("@effected/markdown").FrontmatterValidationError | import("@effected/git").GitCommandError | import("@okfit/profiles").GitHistoryError | import("@okfit/profiles").HumanActorUnresolvedError | import("@okfit/engine").InitOverwriteError | import("@effected/markdown").MarkdownParseError | import("@effected/git").NotARepositoryError | import("effect/PlatformError").PlatformError | import("effect/Schema").SchemaError | import("@effected/git").UnknownRefError | import("@okfit/engine").VerifyConceptNotFoundError | import("@okfit/engine").VerifyUnsupportedFrontmatterError | import("@effected/yaml").YamlParseError, import("@effected/xdg").AppDirs | import("effect/unstable/process/ChildProcessSpawner").ChildProcessSpawner | import("effect/Crypto").Crypto | import("effect/FileSystem").FileSystem | import("@okfit/engine").Now | import("effect/Path").Path | import("@effected/xdg").Xdg>;
23
+ export declare const rootCommand: Command.Command<"okfit", {} | {}, {}, import("@okfit/profiles").AgentActorUnconfiguredError | import("@okfit/core").BundleDepthExceededError | import("@okfit/core").BundleReadError | import("@okfit/core").BundleRootNotFoundError | import("@effected/config-file").ConfigCodecError | import("@effected/config-file").ConfigFileReadError | import("@effected/config-file").ConfigFileWriteError | import("@okfit/engine").ConfigMalformedError | import("@okfit/engine").ConfigPathNotFoundError | import("@effected/config-file").ConfigValidationError | import("@effected/markdown").FrontmatterEncodeError | import("@effected/markdown").FrontmatterFormatMismatchError | import("@effected/markdown").FrontmatterValidationError | import("@effected/git").GitCommandError | import("@okfit/profiles").GitHistoryError | import("@okfit/profiles").HumanActorUnresolvedError | import("@okfit/engine").InitOverwriteError | import("@effected/markdown").MarkdownParseError | import("@effected/git").NotARepositoryError | import("effect/PlatformError").PlatformError | import("effect/Schema").SchemaError | import("@effected/git").UnknownRefError | import("@okfit/engine").VerifyConceptNotFoundError | import("@okfit/engine").VerifyUnsupportedFrontmatterError | import("@effected/yaml").YamlParseError, import("@effected/xdg").AppDirs | import("effect/unstable/process/ChildProcessSpawner").ChildProcessSpawner | import("effect/Crypto").Crypto | import("effect/FileSystem").FileSystem | import("@okfit/engine").Now | import("effect/Path").Path | import("@effected/xdg").Xdg>;
24
24
  //#endregion
25
25
  //#region src/errors.d.ts
26
26
  /**
@@ -66,8 +66,9 @@ export declare const renderFailure: (error: unknown) => ReadonlyArray<string>;
66
66
  //#endregion
67
67
  //#region src/render/context.d.ts
68
68
  /**
69
- * The `human` format: a short header block, then one line per type and one
70
- * per tag. Pure; the caller pipes each line through `Console.log`.
69
+ * The `human` format: a short header block, then one line per type (plus
70
+ * its constraint lines, when it has any) and one per tag. Pure; the caller
71
+ * pipes each line through `Console.log`.
71
72
  *
72
73
  * The `profile:` line reads `profile: (none) (requested NAME, unknown)`
73
74
  * when `profile` and `profile_requested` disagree over an actually-unknown
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@okfit/cli",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
4
4
  "private": false,
5
5
  "description": "The okfit command line: validate, lint, index, and inspect Open Knowledge Format (OKF) bundles.",
6
6
  "keywords": [
@@ -43,18 +43,18 @@
43
43
  },
44
44
  "dependencies": {
45
45
  "@effect/platform-node": "4.0.0-rc.115",
46
- "@effected/cli": "^0.4.0",
47
- "@effected/config-file": "^0.8.0",
48
- "@effected/git": "^0.15.0",
46
+ "@effected/cli": "^0.4.1",
47
+ "@effected/config-file": "^0.9.0",
48
+ "@effected/git": "^0.15.1",
49
49
  "@effected/glob": "^0.6.0",
50
50
  "@effected/jsonc": "^0.11.0",
51
51
  "@effected/markdown": "^0.10.0",
52
52
  "@effected/toml": "^0.7.0",
53
- "@effected/walker": "^0.8.0",
54
- "@effected/yaml": "^0.15.0",
55
- "@okfit/core": "0.3.1",
56
- "@okfit/engine": "0.2.1",
57
- "@okfit/profiles": "0.3.1",
53
+ "@effected/walker": "^0.9.0",
54
+ "@effected/yaml": "^0.15.1",
55
+ "@okfit/core": "0.4.1",
56
+ "@okfit/engine": "0.4.0",
57
+ "@okfit/profiles": "0.5.0",
58
58
  "effect": "4.0.0-rc.115"
59
59
  },
60
60
  "engines": {
package/render/context.js CHANGED
@@ -1,7 +1,20 @@
1
1
  //#region src/render/context.ts
2
+ /** `kind (a | b)` for an enum field, `resource (path)` for a path field, bare `layer` for free text. */
3
+ const renderField = (field) => field.values !== null ? `${field.name} (${field.values.map((v) => v.name).join(" | ")})` : field.kind !== null ? `${field.name} (${field.kind})` : field.name;
2
4
  /**
3
- * The `human` format: a short header block, then one line per type and one
4
- * per tag. Pure; the caller pipes each line through `Console.log`.
5
+ * The constraint lines under one type bullet (issue #33): what `validate`
6
+ * will require of a concept of this type, so an agent learns it here and
7
+ * not from a lint error. Empty for a type that declares none.
8
+ */
9
+ const constraintLines = (type) => [
10
+ ...type.required !== null && type.required.length > 0 ? [` required: ${type.required.join(", ")}`] : [],
11
+ ...type.require_verified === true ? [" verified: required"] : [],
12
+ ...type.fields.length > 0 ? [` fields: ${type.fields.map(renderField).join(", ")}`] : []
13
+ ];
14
+ /**
15
+ * The `human` format: a short header block, then one line per type (plus
16
+ * its constraint lines, when it has any) and one per tag. Pure; the caller
17
+ * pipes each line through `Console.log`.
5
18
  *
6
19
  * The `profile:` line reads `profile: (none) (requested NAME, unknown)`
7
20
  * when `profile` and `profile_requested` disagree over an actually-unknown
@@ -19,7 +32,7 @@ const humanContext = (envelope) => [
19
32
  `agent: ${envelope.actors.agent ?? "(unset)"}`,
20
33
  "",
21
34
  "types:",
22
- ...envelope.types.map((t) => ` ${t.name} ${t.description ?? ""}`),
35
+ ...envelope.types.flatMap((t) => [` ${t.name} ${t.description ?? ""}`, ...constraintLines(t)]),
23
36
  "",
24
37
  "tags:",
25
38
  ...envelope.tags.map((t) => ` ${t.name} ${t.description ?? ""}`)
@@ -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 };
package/version.js CHANGED
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * @public
10
10
  */
11
- const CLI_VERSION = "0.4.1";
11
+ const CLI_VERSION = "0.5.1";
12
12
 
13
13
  //#endregion
14
14
  export { CLI_VERSION };