@dogsbay/commands 0.2.0-beta.101

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 (86) hide show
  1. package/README.md +137 -0
  2. package/dist/commands/content.d.ts +78 -0
  3. package/dist/commands/content.d.ts.map +1 -0
  4. package/dist/commands/content.js +634 -0
  5. package/dist/commands/content.js.map +1 -0
  6. package/dist/commands/examples.d.ts +49 -0
  7. package/dist/commands/examples.d.ts.map +1 -0
  8. package/dist/commands/examples.js +173 -0
  9. package/dist/commands/examples.js.map +1 -0
  10. package/dist/commands/keys.d.ts +14 -0
  11. package/dist/commands/keys.d.ts.map +1 -0
  12. package/dist/commands/keys.js +87 -0
  13. package/dist/commands/keys.js.map +1 -0
  14. package/dist/commands/link-retarget.d.ts +18 -0
  15. package/dist/commands/link-retarget.d.ts.map +1 -0
  16. package/dist/commands/link-retarget.js +104 -0
  17. package/dist/commands/link-retarget.js.map +1 -0
  18. package/dist/commands/refactor-files.d.ts +27 -0
  19. package/dist/commands/refactor-files.d.ts.map +1 -0
  20. package/dist/commands/refactor-files.js +264 -0
  21. package/dist/commands/refactor-files.js.map +1 -0
  22. package/dist/commands/refactor-keys.d.ts +21 -0
  23. package/dist/commands/refactor-keys.d.ts.map +1 -0
  24. package/dist/commands/refactor-keys.js +224 -0
  25. package/dist/commands/refactor-keys.js.map +1 -0
  26. package/dist/commands/refactor-structure.d.ts +19 -0
  27. package/dist/commands/refactor-structure.d.ts.map +1 -0
  28. package/dist/commands/refactor-structure.js +314 -0
  29. package/dist/commands/refactor-structure.js.map +1 -0
  30. package/dist/commands/reindex.d.ts +10 -0
  31. package/dist/commands/reindex.d.ts.map +1 -0
  32. package/dist/commands/reindex.js +23 -0
  33. package/dist/commands/reindex.js.map +1 -0
  34. package/dist/commands/topicmap-edit.d.ts +65 -0
  35. package/dist/commands/topicmap-edit.d.ts.map +1 -0
  36. package/dist/commands/topicmap-edit.js +194 -0
  37. package/dist/commands/topicmap-edit.js.map +1 -0
  38. package/dist/commands/topicmap.d.ts +13 -0
  39. package/dist/commands/topicmap.d.ts.map +1 -0
  40. package/dist/commands/topicmap.js +69 -0
  41. package/dist/commands/topicmap.js.map +1 -0
  42. package/dist/commands/validate.d.ts +13 -0
  43. package/dist/commands/validate.d.ts.map +1 -0
  44. package/dist/commands/validate.js +753 -0
  45. package/dist/commands/validate.js.map +1 -0
  46. package/dist/commands/where-used.d.ts +16 -0
  47. package/dist/commands/where-used.d.ts.map +1 -0
  48. package/dist/commands/where-used.js +83 -0
  49. package/dist/commands/where-used.js.map +1 -0
  50. package/dist/edits.d.ts +54 -0
  51. package/dist/edits.d.ts.map +1 -0
  52. package/dist/edits.js +158 -0
  53. package/dist/edits.js.map +1 -0
  54. package/dist/engine.d.ts +103 -0
  55. package/dist/engine.d.ts.map +1 -0
  56. package/dist/engine.js +115 -0
  57. package/dist/engine.js.map +1 -0
  58. package/dist/flags.d.ts +31 -0
  59. package/dist/flags.d.ts.map +1 -0
  60. package/dist/flags.js +73 -0
  61. package/dist/flags.js.map +1 -0
  62. package/dist/frontmatter-schema.d.ts +97 -0
  63. package/dist/frontmatter-schema.d.ts.map +1 -0
  64. package/dist/frontmatter-schema.js +240 -0
  65. package/dist/frontmatter-schema.js.map +1 -0
  66. package/dist/index.d.ts +35 -0
  67. package/dist/index.d.ts.map +1 -0
  68. package/dist/index.js +70 -0
  69. package/dist/index.js.map +1 -0
  70. package/dist/indexes.d.ts +175 -0
  71. package/dist/indexes.d.ts.map +1 -0
  72. package/dist/indexes.js +536 -0
  73. package/dist/indexes.js.map +1 -0
  74. package/dist/json-schema.d.ts +10 -0
  75. package/dist/json-schema.d.ts.map +1 -0
  76. package/dist/json-schema.js +50 -0
  77. package/dist/json-schema.js.map +1 -0
  78. package/dist/storage.d.ts +68 -0
  79. package/dist/storage.d.ts.map +1 -0
  80. package/dist/storage.js +193 -0
  81. package/dist/storage.js.map +1 -0
  82. package/dist/workspace.d.ts +30 -0
  83. package/dist/workspace.d.ts.map +1 -0
  84. package/dist/workspace.js +63 -0
  85. package/dist/workspace.js.map +1 -0
  86. package/package.json +47 -0
package/README.md ADDED
@@ -0,0 +1,137 @@
1
+ # @dogsbay/commands
2
+
3
+ Typed, headless-first command engine for the Dogsbay platform (Plan 04
4
+ Phase 1). One command definition — a name, a zod input schema, and a
5
+ pure handler over a `Workspace` — yields every surface: the `dogsbay`
6
+ CLI generates its flags from the schema, and the JSON-RPC and MCP
7
+ servers (Phase 3) will consume the same registry. Parity between
8
+ surfaces is by construction, enforced by a schema conformance test.
9
+
10
+ ## Architecture
11
+
12
+ ```
13
+ Storage (injected I/O) NodeStorage | MemoryStorage | (Electron IPC …)
14
+ └─ Workspace lazy indexes + invalidate()/reindex()
15
+ └─ CommandContext what every handler sees
16
+ └─ CommandRegistry.run(name, input, ctx) → CommandResult
17
+ ```
18
+
19
+ - **Storage** is the only I/O boundary. Commands never touch `node:fs`.
20
+ All paths are workspace-root-relative posix paths.
21
+ - **Indexes** are built in one pass: per-doc table (frontmatter,
22
+ headings, anchors, links, includes, `key:` uses), topic-map table,
23
+ key-definition table (from `*.keys.yaml`), and the inverted
24
+ inbound-reference graph.
25
+ - **Results** are envelopes, never exceptions: `{ ok, data, issues }`
26
+ for success (issues = workspace problems), `{ ok: false, error }` for
27
+ input/execution failures.
28
+
29
+ ## Built-in commands
30
+
31
+ | Command | What it does |
32
+ |---|---|
33
+ | `validate` | Broken links, missing anchors, missing include targets, undefined keys, key conflicts, malformed keys files, topic-map errors, missing map targets, cross-map cycles |
34
+ | `where-used <target>` | Inbound references to a file, `file#anchor`, or `key:name` — links, images, includes, topic-map entries, key uses |
35
+ | `keys-list` | Every key definition with defining file, conflicts, and uses |
36
+ | `keys-resolve <name>` | Winning definition + all definitions + uses for one key |
37
+ | `topicmap-resolve <map>` | Resolve a topic map (recursive includes + minja variables) through injected storage |
38
+ | `reindex` | Rebuild indexes and report statistics |
39
+
40
+ ## Refactor commands (Plan 04 Phase 2)
41
+
42
+ All reuse-safe: mutations are index lookups plus surgical text edits,
43
+ inbound references are rewritten in the same run, and anything that
44
+ cannot be repaired mechanically becomes an issue — never a silent drop.
45
+ Golden-diff tests pin the exact rewrites, and `validate` must stay
46
+ error-free after every refactor.
47
+
48
+ | Command | What it does |
49
+ |---|---|
50
+ | `rename-file <from> <to>` | Move/rename; rewrites inbound links (fragments kept), includes, and topic-map hrefs, AND the moved doc's own outbound relative references |
51
+ | `safe-delete <file> [--force]` | Delete only when nothing references it (links, includes, maps, key hrefs); `--force` deletes and issues each now-broken referrer |
52
+ | `extract-include <file> --start N --end M --snippet path` | Extract a line range into a snippet, replace it with `{% include %}`; refuses ranges that split a code fence |
53
+ | `rename-key <from> <to>` | Rename across keys files, `key:` links, and `{{ var }}` text references (flagged for attribute-name ambiguity) |
54
+ | `keyify <file> <key>` | Define a key for a file and convert inbound direct links to `key:` references (fragment links skipped with a note) |
55
+ | `split-topic <file>` | Split at level-2 headings into sibling topics + a topic map; inbound `#anchor` links follow their sections |
56
+ | `merge-topics <into> --sources a.md --sources b.md` | Merge sources as anchored H2 sections (headings shifted, links/includes rebased, source-to-source links become same-document anchors); inbound refs re-pointed |
57
+ | `topic-read <file>` | Enriched read: title, frontmatter, section outline (levels, ids, slugs, line ranges), links, includes, key uses, inbound refs, directive count; `--includeBody` |
58
+ | `topic-set-title <file> <title>` | Rewrite the H1 text only (keeps `{#id}`); warns when frontmatter `title` differs |
59
+ | `section-replace <file> <section> --body` | Replace one section's content (heading kept unless `--keepHeading false`); selector = `{#id}`, heading text or slug; ambiguity refused |
60
+ | `block-insert <file> --content [--section --position start\|end] [--line N]` | Insert a block; refuses mid-fence |
61
+ | `block-delete <file> <section>` | Delete a section subtree; warns about orphaned inbound `#anchor` links; refuses the H1 |
62
+ | `props-get <file>` / `props-set <file> --set k=v --unset k` | Frontmatter read / in-place set+unset (only the touched keys' lines change; creates frontmatter when absent) |
63
+ | `map-list <map>` | Topic-map entries with dotted pointers |
64
+ | `map-add-ref <map> <href> [--navtitle --parent --index --format --toc --singlePage]` | Add an entry; dangling local href / self-include refused |
65
+ | `map-move-ref <map> <from> [--toParent --index]` | Move a subtree; into-own-subtree refused |
66
+ | `map-remove-ref <map> <at> [--keepChildren]` | Remove (children go with it and are reported) or hoist |
67
+ | `map-set-props <map> <at> [--navtitle --href --format --toc --singlePage]` | Set/unset (null) entry props; new href must resolve |
68
+
69
+ ## Using the engine
70
+
71
+ ```ts
72
+ import {
73
+ createDefaultRegistry,
74
+ Workspace,
75
+ NodeStorage,
76
+ MemoryStorage,
77
+ } from "@dogsbay/commands";
78
+
79
+ const registry = createDefaultRegistry();
80
+ const workspace = new Workspace(new NodeStorage("/path/to/docs"));
81
+
82
+ const result = await registry.run("validate", {}, { workspace });
83
+ if (result.ok) {
84
+ console.log(result.summary, result.issues);
85
+ }
86
+ ```
87
+
88
+ In tests (or a browser), swap the storage:
89
+
90
+ ```ts
91
+ const workspace = new Workspace(
92
+ new MemoryStorage({ "index.md": "# Home\n[link](guide.md)" })
93
+ );
94
+ ```
95
+
96
+ ## Defining a command
97
+
98
+ ```ts
99
+ import { z } from "zod";
100
+ import { defineCommand } from "@dogsbay/commands";
101
+
102
+ export const outlineCommand = defineCommand({
103
+ name: "outline",
104
+ description: "Heading outline of one document",
105
+ input: z.object({
106
+ file: z.string().min(1).describe("Workspace-relative .md path"),
107
+ }).strict(),
108
+ positionals: ["file"], // CLI: `dogsbay outline <file>`
109
+ async run(ctx, { file }) {
110
+ const index = await ctx.workspace.index();
111
+ const doc = index.docs.get(file);
112
+ return { data: { headings: doc?.headings ?? [] } };
113
+ },
114
+ });
115
+ ```
116
+
117
+ Register it and every surface picks it up. Schema fields may be:
118
+ `string`, `number`, `boolean`, `enum`, `array(string)`,
119
+ `record(string)` — each optionally `.optional()`, `.default()`,
120
+ `.describe()`. The conformance test (`tests/engine.test.ts`) fails CI
121
+ if a registered command uses anything the surface generators cannot
122
+ express.
123
+
124
+ ## CLI surface
125
+
126
+ Every command is available on the `dogsbay` CLI with uniform flags:
127
+
128
+ ```bash
129
+ dogsbay validate --root docs/ # exit 1 on errors
130
+ dogsbay where-used guide/setup.md --root docs/
131
+ dogsbay where-used key:product-name --root docs/ --json
132
+ dogsbay keys-list --root docs/
133
+ dogsbay topicmap-resolve root.topicmap.yml --variables product=Acme --root docs/
134
+ ```
135
+
136
+ `--root <dir>` selects the workspace (default: cwd); `--json` prints
137
+ the full machine-readable envelope for CI and agents.
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Content verbs — the agent broker's typed alternative to "edit the file"
3
+ * (plans/agent-broker.md Phase 3): `topic-read`, `topic-set-title`,
4
+ * `section-replace`, `block-insert`, `block-delete`, `props-get`,
5
+ * `props-set`.
6
+ *
7
+ * All of them are LINE-WISE edits on the shared fence grammar (a `## x`
8
+ * inside a code example is never structure) with the frontmatter split
9
+ * off first, so every byte outside the edited span is untouched by
10
+ * construction — no parse/serialize round-trip that could normalise
11
+ * directives, tables, or whitespace elsewhere in the file. Every mutation
12
+ * invalidates the workspace index and reports what changed; what cannot
13
+ * be done safely (a mid-fence insert, an ambiguous section, deleting the
14
+ * H1) is refused with an issue, never guessed.
15
+ */
16
+ import { z } from "zod";
17
+ export interface SectionInfo {
18
+ level: number;
19
+ text: string;
20
+ id?: string;
21
+ /** The anchor the site assigns: the explicit id, else the deduped text slug (`usage`, `usage-1`). */
22
+ slug: string;
23
+ /** 0-based line index of the heading within the BODY (after frontmatter). */
24
+ line: number;
25
+ /** 0-based exclusive end line of the section (next heading of level ≤ this, or EOF). */
26
+ end: number;
27
+ /** 0-based exclusive end of the section's OWN text (before its first sub-heading). */
28
+ ownEnd: number;
29
+ }
30
+ /**
31
+ * Divergence gate (plans/ide-change-review.md §3): a short content hash the
32
+ * agent gets from `topic-read` and hands back as `expect` on a mutation. If
33
+ * the target changed in between (a human typing, another agent), the verb
34
+ * refuses with the current hash instead of writing over the newer text.
35
+ */
36
+ export declare const contentHash: (text: string) => string;
37
+ export declare const topicReadCommand: import("../engine.js").CommandDef<z.ZodObject<{
38
+ file: z.ZodString;
39
+ includeBody: z.ZodDefault<z.ZodBoolean>;
40
+ }, z.core.$strict>>;
41
+ export declare const topicSetTitleCommand: import("../engine.js").CommandDef<z.ZodObject<{
42
+ file: z.ZodString;
43
+ title: z.ZodString;
44
+ expect: z.ZodOptional<z.ZodString>;
45
+ }, z.core.$strict>>;
46
+ export declare const sectionReplaceCommand: import("../engine.js").CommandDef<z.ZodObject<{
47
+ file: z.ZodString;
48
+ section: z.ZodString;
49
+ body: z.ZodString;
50
+ keepHeading: z.ZodDefault<z.ZodBoolean>;
51
+ expect: z.ZodOptional<z.ZodString>;
52
+ }, z.core.$strict>>;
53
+ export declare const blockInsertCommand: import("../engine.js").CommandDef<z.ZodObject<{
54
+ file: z.ZodString;
55
+ content: z.ZodString;
56
+ section: z.ZodOptional<z.ZodString>;
57
+ position: z.ZodDefault<z.ZodEnum<{
58
+ start: "start";
59
+ end: "end";
60
+ }>>;
61
+ line: z.ZodOptional<z.ZodNumber>;
62
+ expect: z.ZodOptional<z.ZodString>;
63
+ }, z.core.$strict>>;
64
+ export declare const blockDeleteCommand: import("../engine.js").CommandDef<z.ZodObject<{
65
+ file: z.ZodString;
66
+ section: z.ZodString;
67
+ expect: z.ZodOptional<z.ZodString>;
68
+ }, z.core.$strict>>;
69
+ export declare const propsGetCommand: import("../engine.js").CommandDef<z.ZodObject<{
70
+ file: z.ZodString;
71
+ }, z.core.$strict>>;
72
+ export declare const propsSetCommand: import("../engine.js").CommandDef<z.ZodObject<{
73
+ file: z.ZodString;
74
+ set: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>>;
75
+ unset: z.ZodDefault<z.ZodArray<z.ZodString>>;
76
+ expect: z.ZodOptional<z.ZodString>;
77
+ }, z.core.$strict>>;
78
+ //# sourceMappingURL=content.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../../src/commands/content.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAkBxB,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,qGAAqG;IACrG,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,wFAAwF;IACxF,GAAG,EAAE,MAAM,CAAC;IACZ,sFAAsF;IACtF,MAAM,EAAE,MAAM,CAAC;CAChB;AAiFD;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,MAAM,KAAG,MAAsE,CAAC;AAqGlH,eAAO,MAAM,gBAAgB;;;mBA4D3B,CAAC;AAIH,eAAO,MAAM,oBAAoB;;;;mBAsC/B,CAAC;AAIH,eAAO,MAAM,qBAAqB;;;;;;mBA2ChC,CAAC;AAIH,eAAO,MAAM,kBAAkB;;;;;;;;;;mBA6D7B,CAAC;AAIH,eAAO,MAAM,kBAAkB;;;;mBA2C7B,CAAC;AAIH,eAAO,MAAM,eAAe;;mBAc1B,CAAC;AAKH,eAAO,MAAM,eAAe;;;;;mBAkG1B,CAAC"}