okforge 1.0.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.
Files changed (41) hide show
  1. package/.claude/skills/okf/README.md +141 -0
  2. package/.claude/skills/okf/SKILL.md +124 -0
  3. package/LICENSE +21 -0
  4. package/README.md +72 -0
  5. package/dist/cli.d.ts +3 -0
  6. package/dist/cli.d.ts.map +1 -0
  7. package/dist/cli.js +109 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/commands/check_command.d.ts +15 -0
  10. package/dist/commands/check_command.d.ts.map +1 -0
  11. package/dist/commands/check_command.js +17 -0
  12. package/dist/commands/check_command.js.map +1 -0
  13. package/dist/commands/folders_command.d.ts +6 -0
  14. package/dist/commands/folders_command.d.ts.map +1 -0
  15. package/dist/commands/folders_command.js +9 -0
  16. package/dist/commands/folders_command.js.map +1 -0
  17. package/dist/commands/install_command.d.ts +25 -0
  18. package/dist/commands/install_command.d.ts.map +1 -0
  19. package/dist/commands/install_command.js +45 -0
  20. package/dist/commands/install_command.js.map +1 -0
  21. package/dist/commands/map_command.d.ts +6 -0
  22. package/dist/commands/map_command.d.ts.map +1 -0
  23. package/dist/commands/map_command.js +16 -0
  24. package/dist/commands/map_command.js.map +1 -0
  25. package/dist/commands/nudge_command.d.ts +32 -0
  26. package/dist/commands/nudge_command.d.ts.map +1 -0
  27. package/dist/commands/nudge_command.js +72 -0
  28. package/dist/commands/nudge_command.js.map +1 -0
  29. package/dist/commands/sources_command.d.ts +6 -0
  30. package/dist/commands/sources_command.d.ts.map +1 -0
  31. package/dist/commands/sources_command.js +13 -0
  32. package/dist/commands/sources_command.js.map +1 -0
  33. package/dist/commands/stale_command.d.ts +6 -0
  34. package/dist/commands/stale_command.d.ts.map +1 -0
  35. package/dist/commands/stale_command.js +9 -0
  36. package/dist/commands/stale_command.js.map +1 -0
  37. package/dist/misc/okf_store.d.ts +78 -0
  38. package/dist/misc/okf_store.d.ts.map +1 -0
  39. package/dist/misc/okf_store.js +261 -0
  40. package/dist/misc/okf_store.js.map +1 -0
  41. package/package.json +47 -0
@@ -0,0 +1,16 @@
1
+ import { OkfStore } from '../misc/okf_store.js';
2
+ /** `map` — print the full folder -> sources mapping. */
3
+ export class MapCommand {
4
+ /** The mapping as printable lines: `## <folder>` headers with ` - <source>` rows. */
5
+ static map(cwd) {
6
+ const lines = [];
7
+ for (const folder of OkfStore.folders(cwd)) {
8
+ lines.push(`## ${folder}`);
9
+ for (const source of OkfStore.sources(cwd, folder)) {
10
+ lines.push(` - ${source}`);
11
+ }
12
+ }
13
+ return lines;
14
+ }
15
+ }
16
+ //# sourceMappingURL=map_command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"map_command.js","sourceRoot":"","sources":["../../src/commands/map_command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEhD,wDAAwD;AACxD,MAAM,OAAO,UAAU;IACtB,sFAAsF;IACtF,MAAM,CAAC,GAAG,CAAC,GAAW;QACrB,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,KAAK,MAAM,MAAM,IAAI,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAC5C,KAAK,CAAC,IAAI,CAAC,MAAM,MAAM,EAAE,CAAC,CAAC;YAC3B,KAAK,MAAM,MAAM,IAAI,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,CAAC;gBACpD,KAAK,CAAC,IAAI,CAAC,OAAO,MAAM,EAAE,CAAC,CAAC;YAC7B,CAAC;QACF,CAAC;QACD,OAAO,KAAK,CAAC;IACd,CAAC;CACD"}
@@ -0,0 +1,32 @@
1
+ import { z } from 'zod';
2
+ /** Stop-hook payload read from stdin; only these flat fields are used. */
3
+ declare const HookInputSchema: z.ZodObject<{
4
+ session_id: z.ZodOptional<z.ZodString>;
5
+ cwd: z.ZodOptional<z.ZodString>;
6
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
7
+ session_id: z.ZodOptional<z.ZodString>;
8
+ cwd: z.ZodOptional<z.ZodString>;
9
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
10
+ session_id: z.ZodOptional<z.ZodString>;
11
+ cwd: z.ZodOptional<z.ZodString>;
12
+ }, z.ZodTypeAny, "passthrough">>;
13
+ /** Parsed Stop-hook payload. */
14
+ export type HookInput = z.infer<typeof HookInputSchema>;
15
+ /**
16
+ * Stop-hook companion to the okf skill. When a session changed source that an OKF
17
+ * folder is derived from but left `okf/` untouched, it prints a gentle,
18
+ * non-blocking reminder to refresh the affected docs. It fires at most once per
19
+ * session and stays silent when `okf/` was already touched. The folder <-> source
20
+ * mapping lives in the repository's `.okforge.config.json`, so the hook and the
21
+ * skill read a single source of truth.
22
+ */
23
+ export declare class NudgeCommand {
24
+ /** Read the hook payload from stdin and evaluate the nudge; never throws. */
25
+ static nudge(): Promise<void>;
26
+ /** Decide whether to nudge for this payload, printing the message when it fires. */
27
+ static evaluate(input: HookInput): void;
28
+ /** Read all of stdin as UTF-8 text. */
29
+ static readStdin(): Promise<string>;
30
+ }
31
+ export {};
32
+ //# sourceMappingURL=nudge_command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nudge_command.d.ts","sourceRoot":"","sources":["../../src/commands/nudge_command.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,0EAA0E;AAC1E,QAAA,MAAM,eAAe;;;;;;;;;gCAKN,CAAC;AAEhB,gCAAgC;AAChC,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD;;;;;;;GAOG;AACH,qBAAa,YAAY;IACxB,6EAA6E;WAChE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAenC,oFAAoF;IACpF,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IA2BvC,uCAAuC;WAC1B,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;CAOzC"}
@@ -0,0 +1,72 @@
1
+ import Fs from 'node:fs';
2
+ import Path from 'node:path';
3
+ import Os from 'node:os';
4
+ import { z } from 'zod';
5
+ import { OkfStore } from '../misc/okf_store.js';
6
+ /** Stop-hook payload read from stdin; only these flat fields are used. */
7
+ const HookInputSchema = z
8
+ .object({
9
+ session_id: z.string().optional(),
10
+ cwd: z.string().optional(),
11
+ })
12
+ .passthrough();
13
+ /**
14
+ * Stop-hook companion to the okf skill. When a session changed source that an OKF
15
+ * folder is derived from but left `okf/` untouched, it prints a gentle,
16
+ * non-blocking reminder to refresh the affected docs. It fires at most once per
17
+ * session and stays silent when `okf/` was already touched. The folder <-> source
18
+ * mapping lives in the repository's `.okforge.config.json`, so the hook and the
19
+ * skill read a single source of truth.
20
+ */
21
+ export class NudgeCommand {
22
+ /** Read the hook payload from stdin and evaluate the nudge; never throws. */
23
+ static async nudge() {
24
+ try {
25
+ const raw = await NudgeCommand.readStdin();
26
+ let input = {};
27
+ try {
28
+ input = HookInputSchema.parse(JSON.parse(raw));
29
+ }
30
+ catch {
31
+ input = {};
32
+ }
33
+ NudgeCommand.evaluate(input);
34
+ }
35
+ catch {
36
+ // A Stop hook must never break the session; stay silent on any failure.
37
+ }
38
+ }
39
+ /** Decide whether to nudge for this payload, printing the message when it fires. */
40
+ static evaluate(input) {
41
+ const cwd = input.cwd !== undefined && input.cwd !== '' ? input.cwd : process.cwd();
42
+ const sessionId = input.session_id !== undefined && input.session_id !== '' ? input.session_id : 'nosession';
43
+ const marker = Path.join(Os.tmpdir(), `claude-okf-nudge-${sessionId}`);
44
+ // Already nudged this session, not a git repo, or okf/ already being touched.
45
+ if (Fs.existsSync(marker) === true) {
46
+ return;
47
+ }
48
+ if (OkfStore.isGitRepo(cwd) === false) {
49
+ return;
50
+ }
51
+ if (OkfStore.git(['status', '--porcelain', '--', 'okf'], cwd) !== '') {
52
+ return;
53
+ }
54
+ const stale = OkfStore.staleFolders(cwd);
55
+ if (stale.length === 0) {
56
+ return;
57
+ }
58
+ Fs.writeFileSync(marker, '');
59
+ const folders = stale.map((entry) => entry.folder).join(', ');
60
+ const message = `Source documented by the OKF bundle changed this session (${folders}) but okf/ was not updated. Consider running /okf refresh <folder> to keep the knowledge bundle in sync.`;
61
+ process.stdout.write(`${JSON.stringify({ systemMessage: message })}\n`);
62
+ }
63
+ /** Read all of stdin as UTF-8 text. */
64
+ static async readStdin() {
65
+ const chunks = [];
66
+ for await (const chunk of process.stdin) {
67
+ chunks.push(Buffer.isBuffer(chunk) === true ? chunk : Buffer.from(chunk));
68
+ }
69
+ return Buffer.concat(chunks).toString('utf-8');
70
+ }
71
+ }
72
+ //# sourceMappingURL=nudge_command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nudge_command.js","sourceRoot":"","sources":["../../src/commands/nudge_command.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /** `sources` — print the source paths a folder is derived from. */
2
+ export declare class SourcesCommand {
3
+ /** The source path prefixes `folder` is derived from; throws when undeclared. */
4
+ static sources(cwd: string, folder: string): string[];
5
+ }
6
+ //# sourceMappingURL=sources_command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sources_command.d.ts","sourceRoot":"","sources":["../../src/commands/sources_command.ts"],"names":[],"mappings":"AAEA,mEAAmE;AACnE,qBAAa,cAAc;IAC1B,iFAAiF;IACjF,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE;CAOrD"}
@@ -0,0 +1,13 @@
1
+ import { OkfStore } from '../misc/okf_store.js';
2
+ /** `sources` — print the source paths a folder is derived from. */
3
+ export class SourcesCommand {
4
+ /** The source path prefixes `folder` is derived from; throws when undeclared. */
5
+ static sources(cwd, folder) {
6
+ const prefixes = OkfStore.sources(cwd, folder);
7
+ if (prefixes.length === 0) {
8
+ throw new Error(`unknown folder: ${folder}`);
9
+ }
10
+ return prefixes;
11
+ }
12
+ }
13
+ //# sourceMappingURL=sources_command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sources_command.js","sourceRoot":"","sources":["../../src/commands/sources_command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEhD,mEAAmE;AACnE,MAAM,OAAO,cAAc;IAC1B,iFAAiF;IACjF,MAAM,CAAC,OAAO,CAAC,GAAW,EAAE,MAAc;QACzC,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAC/C,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,mBAAmB,MAAM,EAAE,CAAC,CAAC;QAC9C,CAAC;QACD,OAAO,QAAQ,CAAC;IACjB,CAAC;CACD"}
@@ -0,0 +1,6 @@
1
+ /** `stale` — folders whose source changed since HEAD while the folder was not edited. */
2
+ export declare class StaleCommand {
3
+ /** Printable lines, one per stale folder: `<folder> (source changed: <path>)`. */
4
+ static stale(cwd: string): string[];
5
+ }
6
+ //# sourceMappingURL=stale_command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stale_command.d.ts","sourceRoot":"","sources":["../../src/commands/stale_command.ts"],"names":[],"mappings":"AAEA,yFAAyF;AACzF,qBAAa,YAAY;IACxB,kFAAkF;IAClF,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE;CAGnC"}
@@ -0,0 +1,9 @@
1
+ import { OkfStore } from '../misc/okf_store.js';
2
+ /** `stale` — folders whose source changed since HEAD while the folder was not edited. */
3
+ export class StaleCommand {
4
+ /** Printable lines, one per stale folder: `<folder> (source changed: <path>)`. */
5
+ static stale(cwd) {
6
+ return OkfStore.staleFolders(cwd).map(({ folder, source }) => `${folder} (source changed: ${source})`);
7
+ }
8
+ }
9
+ //# sourceMappingURL=stale_command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stale_command.js","sourceRoot":"","sources":["../../src/commands/stale_command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEhD,yFAAyF;AACzF,MAAM,OAAO,YAAY;IACxB,kFAAkF;IAClF,MAAM,CAAC,KAAK,CAAC,GAAW;QACvB,OAAO,QAAQ,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,GAAG,MAAM,qBAAqB,MAAM,GAAG,CAAC,CAAC;IACxG,CAAC;CACD"}
@@ -0,0 +1,78 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * The okforge config: the folder <-> source mapping for a repository. Each key
4
+ * is an OKF concept folder; each value is the list of source path prefixes that
5
+ * folder is derived from. A changed file under any prefix marks the folder stale.
6
+ * This is per-repository data — okforge ships with none of it.
7
+ */
8
+ declare const OkfConfigSchema: z.ZodObject<{
9
+ folders: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString, "many">>>;
10
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
11
+ folders: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString, "many">>>;
12
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
13
+ folders: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString, "many">>>;
14
+ }, z.ZodTypeAny, "passthrough">>;
15
+ /** Parsed okforge config. */
16
+ export type OkfConfig = z.infer<typeof OkfConfigSchema>;
17
+ /** A folder whose source changed without the folder itself being edited. */
18
+ export type StaleFolder = {
19
+ folder: string;
20
+ source: string;
21
+ };
22
+ /** A single file or directory yielded by {@link OkfStore.walk}. */
23
+ export type WalkEntry = {
24
+ path: string;
25
+ isDirectory: boolean;
26
+ };
27
+ /**
28
+ * Primitive mechanics shared by the okf subcommands: loading the per-repository
29
+ * folder <-> source mapping, git inspection, staleness detection, and the
30
+ * conformance / dead-link lint. The command classes orchestrate these primitives.
31
+ */
32
+ export declare class OkfStore {
33
+ /** Absolute path of the config file for the repository at `cwd`. */
34
+ static configPath(cwd: string): string;
35
+ /**
36
+ * The folder <-> source mapping for the repository at `cwd`. Returns an empty
37
+ * mapping when no config exists, so okforge is a no-op until a repo declares
38
+ * one. Throws when the config is present but malformed.
39
+ */
40
+ static loadConfig(cwd: string): OkfConfig;
41
+ /** The OKF concept folder names declared in `cwd`'s config, in declared order. */
42
+ static folders(cwd: string): string[];
43
+ /** The source path prefixes `folder` is derived from, or `[]` when undeclared. */
44
+ static sources(cwd: string, folder: string): string[];
45
+ /** Run `git` in `cwd`, returning its stdout, or '' when the command fails. */
46
+ static git(args: string[], cwd: string): string;
47
+ /** Whether `cwd` is inside a git working tree. */
48
+ static isGitRepo(cwd: string): boolean;
49
+ /** Tracked-and-untracked paths changed since HEAD, sorted and de-duplicated. */
50
+ static changedPaths(cwd: string): string[];
51
+ /**
52
+ * Folders whose source changed since HEAD while the folder itself was not
53
+ * edited this session. Folders already being edited under `okf/<folder>` are
54
+ * skipped so the user is not nudged about work in progress.
55
+ */
56
+ static staleFolders(cwd: string): StaleFolder[];
57
+ /** First path in `changed` containing any of `prefixes` (substring match), or null. */
58
+ static firstMatch(changed: string[], prefixes: string[]): string | null;
59
+ /**
60
+ * Conformance + dead-link lint of the `okf/` bundle under `cwd`. Returns the
61
+ * list of problems (empty when conformant); throws when there is no bundle.
62
+ * Needs no mapping — it lints the bundle's markdown alone, so it is fully
63
+ * repository-agnostic.
64
+ */
65
+ static check(cwd: string): string[];
66
+ /** The count of concept docs (non-index, non-log `.md`) in the bundle under `cwd`. */
67
+ static conceptCount(cwd: string): number;
68
+ /** Whether `content` opens with a `---` block holding a non-empty `type:`. */
69
+ static hasTypeFrontmatter(content: string): boolean;
70
+ /** Distinct bundle-relative `.md` link targets across every doc, sorted. */
71
+ static bundleLinks(entries: WalkEntry[]): string[];
72
+ /** The first line of `content` (without its trailing newline). */
73
+ static firstLine(content: string): string;
74
+ /** Every file and directory under `dir`, recursively (excluding `dir` itself). */
75
+ static walk(dir: string): WalkEntry[];
76
+ }
77
+ export {};
78
+ //# sourceMappingURL=okf_store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"okf_store.d.ts","sourceRoot":"","sources":["../../src/misc/okf_store.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB;;;;;GAKG;AACH,QAAA,MAAM,eAAe;;;;;;gCAIN,CAAC;AAEhB,6BAA6B;AAC7B,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD,4EAA4E;AAC5E,MAAM,MAAM,WAAW,GAAG;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,mEAAmE;AACnE,MAAM,MAAM,SAAS,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF;;;;GAIG;AACH,qBAAa,QAAQ;IACpB,oEAAoE;IACpE,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAItC;;;;OAIG;IACH,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS;IAmBzC,kFAAkF;IAClF,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE;IAIrC,kFAAkF;IAClF,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE;IAKrD,8EAA8E;IAC9E,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM;IAa/C,kDAAkD;IAClD,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAStC,gFAAgF;IAChF,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE;IAU1C;;;;OAIG;IACH,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,EAAE;IA0B/C,uFAAuF;IACvF,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI;IAavE;;;;;OAKG;IACH,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE;IAoDnC,sFAAsF;IACtF,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAexC,8EAA8E;IAC9E,MAAM,CAAC,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAkBnD,4EAA4E;IAC5E,MAAM,CAAC,WAAW,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,MAAM,EAAE;IAiBlD,kEAAkE;IAClE,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAIzC,kFAAkF;IAClF,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE;CAerC"}
@@ -0,0 +1,261 @@
1
+ import Fs from 'node:fs';
2
+ import Path from 'node:path';
3
+ import ChildProcess from 'node:child_process';
4
+ import { z } from 'zod';
5
+ /** Config file name read from the repository root. */
6
+ const CONFIG_FILENAME = '.okforge.config.json';
7
+ /**
8
+ * The okforge config: the folder <-> source mapping for a repository. Each key
9
+ * is an OKF concept folder; each value is the list of source path prefixes that
10
+ * folder is derived from. A changed file under any prefix marks the folder stale.
11
+ * This is per-repository data — okforge ships with none of it.
12
+ */
13
+ const OkfConfigSchema = z
14
+ .object({
15
+ folders: z.record(z.string(), z.array(z.string())).default({}),
16
+ })
17
+ .passthrough();
18
+ /**
19
+ * Primitive mechanics shared by the okf subcommands: loading the per-repository
20
+ * folder <-> source mapping, git inspection, staleness detection, and the
21
+ * conformance / dead-link lint. The command classes orchestrate these primitives.
22
+ */
23
+ export class OkfStore {
24
+ /** Absolute path of the config file for the repository at `cwd`. */
25
+ static configPath(cwd) {
26
+ return Path.join(cwd, CONFIG_FILENAME);
27
+ }
28
+ /**
29
+ * The folder <-> source mapping for the repository at `cwd`. Returns an empty
30
+ * mapping when no config exists, so okforge is a no-op until a repo declares
31
+ * one. Throws when the config is present but malformed.
32
+ */
33
+ static loadConfig(cwd) {
34
+ const configPath = OkfStore.configPath(cwd);
35
+ if (Fs.existsSync(configPath) === false) {
36
+ return { folders: {} };
37
+ }
38
+ let parsed;
39
+ try {
40
+ parsed = JSON.parse(Fs.readFileSync(configPath, 'utf-8'));
41
+ }
42
+ catch (error) {
43
+ const message = error instanceof Error ? error.message : String(error);
44
+ throw new Error(`invalid JSON in ${CONFIG_FILENAME}: ${message}`);
45
+ }
46
+ const result = OkfConfigSchema.safeParse(parsed);
47
+ if (result.success === false) {
48
+ throw new Error(`invalid ${CONFIG_FILENAME}: ${result.error.issues.map((issue) => issue.message).join('; ')}`);
49
+ }
50
+ return result.data;
51
+ }
52
+ /** The OKF concept folder names declared in `cwd`'s config, in declared order. */
53
+ static folders(cwd) {
54
+ return Object.keys(OkfStore.loadConfig(cwd).folders);
55
+ }
56
+ /** The source path prefixes `folder` is derived from, or `[]` when undeclared. */
57
+ static sources(cwd, folder) {
58
+ const prefixes = OkfStore.loadConfig(cwd).folders[folder];
59
+ return prefixes === undefined ? [] : [...prefixes];
60
+ }
61
+ /** Run `git` in `cwd`, returning its stdout, or '' when the command fails. */
62
+ static git(args, cwd) {
63
+ try {
64
+ return ChildProcess.execFileSync('git', args, {
65
+ cwd,
66
+ encoding: 'utf-8',
67
+ stdio: ['ignore', 'pipe', 'ignore'],
68
+ maxBuffer: 32 * 1024 * 1024,
69
+ });
70
+ }
71
+ catch {
72
+ return '';
73
+ }
74
+ }
75
+ /** Whether `cwd` is inside a git working tree. */
76
+ static isGitRepo(cwd) {
77
+ try {
78
+ ChildProcess.execFileSync('git', ['rev-parse', '--git-dir'], { cwd, stdio: 'ignore' });
79
+ return true;
80
+ }
81
+ catch {
82
+ return false;
83
+ }
84
+ }
85
+ /** Tracked-and-untracked paths changed since HEAD, sorted and de-duplicated. */
86
+ static changedPaths(cwd) {
87
+ const tracked = OkfStore.git(['diff', '--name-only', 'HEAD'], cwd);
88
+ const untracked = OkfStore.git(['ls-files', '--others', '--exclude-standard'], cwd);
89
+ const paths = `${tracked}\n${untracked}`
90
+ .split('\n')
91
+ .map((line) => line.trim())
92
+ .filter((line) => line !== '');
93
+ return [...new Set(paths)].sort();
94
+ }
95
+ /**
96
+ * Folders whose source changed since HEAD while the folder itself was not
97
+ * edited this session. Folders already being edited under `okf/<folder>` are
98
+ * skipped so the user is not nudged about work in progress.
99
+ */
100
+ static staleFolders(cwd) {
101
+ if (OkfStore.isGitRepo(cwd) === false) {
102
+ return [];
103
+ }
104
+ const config = OkfStore.loadConfig(cwd);
105
+ const folders = Object.keys(config.folders);
106
+ if (folders.length === 0) {
107
+ return [];
108
+ }
109
+ const changed = OkfStore.changedPaths(cwd);
110
+ if (changed.length === 0) {
111
+ return [];
112
+ }
113
+ const stale = [];
114
+ for (const folder of folders) {
115
+ if (OkfStore.git(['status', '--porcelain', '--', `okf/${folder}`], cwd) !== '') {
116
+ continue;
117
+ }
118
+ const source = OkfStore.firstMatch(changed, config.folders[folder]);
119
+ if (source !== null) {
120
+ stale.push({ folder, source });
121
+ }
122
+ }
123
+ return stale;
124
+ }
125
+ /** First path in `changed` containing any of `prefixes` (substring match), or null. */
126
+ static firstMatch(changed, prefixes) {
127
+ for (const prefix of prefixes) {
128
+ if (prefix === '') {
129
+ continue;
130
+ }
131
+ const match = changed.find((path) => path.includes(prefix));
132
+ if (match !== undefined) {
133
+ return match;
134
+ }
135
+ }
136
+ return null;
137
+ }
138
+ /**
139
+ * Conformance + dead-link lint of the `okf/` bundle under `cwd`. Returns the
140
+ * list of problems (empty when conformant); throws when there is no bundle.
141
+ * Needs no mapping — it lints the bundle's markdown alone, so it is fully
142
+ * repository-agnostic.
143
+ */
144
+ static check(cwd) {
145
+ const root = Path.join(cwd, 'okf');
146
+ if (Fs.existsSync(root) === false || Fs.statSync(root).isDirectory() === false) {
147
+ throw new Error(`no okf/ bundle at ${root}`);
148
+ }
149
+ const problems = [];
150
+ const entries = OkfStore.walk(root);
151
+ // 1. snake_case names only (no kebab-case in any file or directory name).
152
+ for (const entry of entries) {
153
+ if (Path.basename(entry.path).includes('-') === true) {
154
+ problems.push(`NAME: kebab-case not allowed: ${Path.relative(cwd, entry.path)}`);
155
+ }
156
+ }
157
+ // 2. every non-index .md has frontmatter with a non-empty type.
158
+ for (const entry of entries) {
159
+ if (entry.isDirectory === true) {
160
+ continue;
161
+ }
162
+ const name = Path.basename(entry.path);
163
+ if (name.endsWith('.md') === false || name === 'index.md' || name === 'log.md') {
164
+ continue;
165
+ }
166
+ if (OkfStore.hasTypeFrontmatter(Fs.readFileSync(entry.path, 'utf-8')) === false) {
167
+ problems.push(`TYPE: missing/empty frontmatter type: ${Path.relative(cwd, entry.path)}`);
168
+ }
169
+ }
170
+ // 3. sub-folder index.md must carry no frontmatter (root index.md may).
171
+ for (const entry of entries) {
172
+ if (entry.isDirectory === true || Path.basename(entry.path) !== 'index.md') {
173
+ continue;
174
+ }
175
+ if (entry.path === Path.join(root, 'index.md')) {
176
+ continue;
177
+ }
178
+ if (OkfStore.firstLine(Fs.readFileSync(entry.path, 'utf-8')) === '---') {
179
+ problems.push(`INDEX: sub-folder index.md must not have frontmatter: ${Path.relative(cwd, entry.path)}`);
180
+ }
181
+ }
182
+ // 4. bundle-relative links (/foo/bar.md) must resolve to a file.
183
+ for (const link of OkfStore.bundleLinks(entries)) {
184
+ if (Fs.existsSync(Path.join(root, link)) === false) {
185
+ problems.push(`LINK: dangling bundle link: ${link}`);
186
+ }
187
+ }
188
+ return problems;
189
+ }
190
+ /** The count of concept docs (non-index, non-log `.md`) in the bundle under `cwd`. */
191
+ static conceptCount(cwd) {
192
+ const root = Path.join(cwd, 'okf');
193
+ let count = 0;
194
+ for (const entry of OkfStore.walk(root)) {
195
+ if (entry.isDirectory === true) {
196
+ continue;
197
+ }
198
+ const name = Path.basename(entry.path);
199
+ if (name.endsWith('.md') === true && name !== 'index.md' && name !== 'log.md') {
200
+ count += 1;
201
+ }
202
+ }
203
+ return count;
204
+ }
205
+ /** Whether `content` opens with a `---` block holding a non-empty `type:`. */
206
+ static hasTypeFrontmatter(content) {
207
+ const lines = content.split('\n');
208
+ if (lines.length === 0 || lines[0] !== '---') {
209
+ return false;
210
+ }
211
+ let seenType = false;
212
+ for (let index = 1; index < lines.length; index += 1) {
213
+ const line = lines[index];
214
+ if (line === '---') {
215
+ return seenType;
216
+ }
217
+ if (/^type:[ \t]*[^ \t]+/.test(line) === true) {
218
+ seenType = true;
219
+ }
220
+ }
221
+ return false;
222
+ }
223
+ /** Distinct bundle-relative `.md` link targets across every doc, sorted. */
224
+ static bundleLinks(entries) {
225
+ const links = new Set();
226
+ const pattern = /\]\((\/[a-z0-9_./-]+\.md)\)/g;
227
+ for (const entry of entries) {
228
+ if (entry.isDirectory === true || entry.path.endsWith('.md') === false) {
229
+ continue;
230
+ }
231
+ const content = Fs.readFileSync(entry.path, 'utf-8');
232
+ let match = pattern.exec(content);
233
+ while (match !== null) {
234
+ links.add(match[1]);
235
+ match = pattern.exec(content);
236
+ }
237
+ }
238
+ return [...links].sort();
239
+ }
240
+ /** The first line of `content` (without its trailing newline). */
241
+ static firstLine(content) {
242
+ return content.split('\n', 1)[0] ?? '';
243
+ }
244
+ /** Every file and directory under `dir`, recursively (excluding `dir` itself). */
245
+ static walk(dir) {
246
+ const result = [];
247
+ if (Fs.existsSync(dir) === false) {
248
+ return result;
249
+ }
250
+ for (const entry of Fs.readdirSync(dir, { withFileTypes: true })) {
251
+ const childPath = Path.join(dir, entry.name);
252
+ const isDirectory = entry.isDirectory();
253
+ result.push({ path: childPath, isDirectory });
254
+ if (isDirectory === true) {
255
+ result.push(...OkfStore.walk(childPath));
256
+ }
257
+ }
258
+ return result;
259
+ }
260
+ }
261
+ //# sourceMappingURL=okf_store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"okf_store.js","sourceRoot":"","sources":["../../src/misc/okf_store.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "okforge",
3
+ "version": "1.0.1",
4
+ "description": "Open Knowledge Format (OKF) skill for Claude Code — deterministic okf bundle mechanics and a Stop-hook nudge.",
5
+ "type": "module",
6
+ "bin": {
7
+ "okforge": "dist/cli.js"
8
+ },
9
+ "main": "dist/cli.js",
10
+ "types": "dist/cli.d.ts",
11
+ "files": [
12
+ "dist",
13
+ ".claude/skills",
14
+ "LICENSE"
15
+ ],
16
+ "engines": {
17
+ "node": ">=20.12"
18
+ },
19
+ "scripts": {
20
+ "okforge": "tsx src/cli.ts",
21
+ "typecheck": "tsc --noEmit",
22
+ "build": "tsc -p tsconfig.build.json",
23
+ "publish:all": "npm run build && npm version patch && npm publish"
24
+ },
25
+ "author": {
26
+ "name": "Jerome Etienne",
27
+ "email": "jerome.etienne@gmail.com"
28
+ },
29
+ "license": "MIT",
30
+ "keywords": [
31
+ "claude-code",
32
+ "okf",
33
+ "open-knowledge-format",
34
+ "knowledge-bundle",
35
+ "skill"
36
+ ],
37
+ "dependencies": {
38
+ "chalk": "^5.3.0",
39
+ "commander": "^12.1.0",
40
+ "zod": "^3.23.8"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^22.10.0",
44
+ "tsx": "^4.19.2",
45
+ "typescript": "^5.7.2"
46
+ }
47
+ }