@nusoft/nuos-build-catalogue 0.10.2 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,8 +5,15 @@
5
5
  * 1. mkdir docs/build/ + copy starter-kit content
6
6
  * 2. Substitute {{PROJECT_NAME}} / {{PROJECT_TAGLINE}} / {{TODAY}} in
7
7
  * STATE.md and methodfile.json
8
- * 3. Copy the four protocols into .claude/commands/ (preserving
9
- * existing files)
8
+ * 3. Fan the four protocols out to ALL THREE supported AI coding tools:
9
+ * Claude Code (.claude/commands/<n>.md), OpenCode
10
+ * (.opencode/commands/<n>.md), and Codex CLI
11
+ * (.agents/skills/<n>/SKILL.md). Each tool reads commands from a
12
+ * different path with slightly different frontmatter; the body is
13
+ * identical across all three. We write to all three by default — the
14
+ * files are tiny and harmless if a tool is unused. A consumer of
15
+ * this catalogue on any of the three tools gets working slash
16
+ * commands without extra configuration.
10
17
  * 4. Append a "Build catalogue (NuOS Build Method)" section to
11
18
  * CLAUDE.md (creating it if missing; preserving existing content)
12
19
  * 5. Update .gitignore: !docs/build/ override (if `build/` is present
@@ -16,7 +23,8 @@
16
23
  * 6. Run a first migrate to verify
17
24
  *
18
25
  * Companion command: `install-protocols` refreshes just step 3 from the
19
- * canonical bodies bundled in this CLI package.
26
+ * canonical bodies bundled in this CLI package, also fanning out to all
27
+ * three tool paths.
20
28
  */
21
29
  import type { Prompt } from './prompt.js';
22
30
  export interface InitOptions {
@@ -5,8 +5,15 @@
5
5
  * 1. mkdir docs/build/ + copy starter-kit content
6
6
  * 2. Substitute {{PROJECT_NAME}} / {{PROJECT_TAGLINE}} / {{TODAY}} in
7
7
  * STATE.md and methodfile.json
8
- * 3. Copy the four protocols into .claude/commands/ (preserving
9
- * existing files)
8
+ * 3. Fan the four protocols out to ALL THREE supported AI coding tools:
9
+ * Claude Code (.claude/commands/<n>.md), OpenCode
10
+ * (.opencode/commands/<n>.md), and Codex CLI
11
+ * (.agents/skills/<n>/SKILL.md). Each tool reads commands from a
12
+ * different path with slightly different frontmatter; the body is
13
+ * identical across all three. We write to all three by default — the
14
+ * files are tiny and harmless if a tool is unused. A consumer of
15
+ * this catalogue on any of the three tools gets working slash
16
+ * commands without extra configuration.
10
17
  * 4. Append a "Build catalogue (NuOS Build Method)" section to
11
18
  * CLAUDE.md (creating it if missing; preserving existing content)
12
19
  * 5. Update .gitignore: !docs/build/ override (if `build/` is present
@@ -16,9 +23,10 @@
16
23
  * 6. Run a first migrate to verify
17
24
  *
18
25
  * Companion command: `install-protocols` refreshes just step 3 from the
19
- * canonical bodies bundled in this CLI package.
26
+ * canonical bodies bundled in this CLI package, also fanning out to all
27
+ * three tool paths.
20
28
  */
21
- import { mkdir, readFile, writeFile, copyFile, readdir, access } from 'node:fs/promises';
29
+ import { mkdir, readFile, writeFile, readdir, access } from 'node:fs/promises';
22
30
  import { existsSync, constants } from 'node:fs';
23
31
  import path from 'node:path';
24
32
  import { fileURLToPath } from 'node:url';
@@ -32,6 +40,41 @@ const PROTOCOL_FILES = [
32
40
  'wu-new.md',
33
41
  'persona-new.md',
34
42
  ];
43
+ /**
44
+ * One-line descriptions used in the frontmatter of installed protocol
45
+ * files. Tools surface this text in their command list, so it should
46
+ * read as an imperative summary.
47
+ */
48
+ const PROTOCOL_DESCRIPTIONS = {
49
+ 'start-of-session': 'Read STATE, last session log, active WU; surface next action',
50
+ 'end-of-session': 'Write session log, update STATE + indices, move ✅ WUs to done/, commit',
51
+ 'wu-new': 'Create a new work unit with the six-field outcome shape (per D046)',
52
+ 'persona-new': 'Create a new P-NNN persona with the seven dimensions and acid-test (per D046)',
53
+ };
54
+ const TOOLS = {
55
+ claude: {
56
+ label: 'Claude Code',
57
+ destPath: (slug) => path.join('.claude', 'commands', `${slug}.md`),
58
+ render: (slug, body) => withFrontmatter({ description: PROTOCOL_DESCRIPTIONS[slug] ?? '' }, body),
59
+ },
60
+ opencode: {
61
+ label: 'OpenCode',
62
+ destPath: (slug) => path.join('.opencode', 'commands', `${slug}.md`),
63
+ render: (slug, body) => withFrontmatter({ description: PROTOCOL_DESCRIPTIONS[slug] ?? '' }, body),
64
+ },
65
+ codex: {
66
+ label: 'Codex CLI',
67
+ destPath: (slug) => path.join('.agents', 'skills', slug, 'SKILL.md'),
68
+ render: (slug, body) => withFrontmatter({ name: slug, description: PROTOCOL_DESCRIPTIONS[slug] ?? '' }, body),
69
+ },
70
+ };
71
+ function withFrontmatter(fields, body) {
72
+ const lines = ['---'];
73
+ for (const [k, v] of Object.entries(fields))
74
+ lines.push(`${k}: ${v}`);
75
+ lines.push('---', '', '');
76
+ return lines.join('\n') + body;
77
+ }
35
78
  export async function cmdInit(prompt, options = {}) {
36
79
  const cwd = options.cwd ?? process.cwd();
37
80
  const today = new Date().toISOString().slice(0, 10);
@@ -107,14 +150,20 @@ export async function cmdInit(prompt, options = {}) {
107
150
  log_line(' · writing methodfile.json at repo root');
108
151
  const methodfileSrc = await readFile(path.join(TEMPLATES_ROOT, 'starter-kit', 'methodfile.json'), 'utf8');
109
152
  await writeFile(path.join(cwd, 'methodfile.json'), substitute(methodfileSrc, subs), 'utf8');
110
- // Step 3: copy protocols into .claude/commands/
111
- const claudeCommandsDir = path.join(cwd, '.claude', 'commands');
112
- await mkdir(claudeCommandsDir, { recursive: true });
113
- for (const protocol of PROTOCOL_FILES) {
114
- const dest = path.join(claudeCommandsDir, protocol);
115
- const existed = existsSync(dest);
116
- await copyFile(path.join(TEMPLATES_ROOT, 'protocols', protocol), dest);
117
- log_line(` · ${existed ? 'overwrote' : 'installed'} .claude/commands/${protocol}`);
153
+ // Step 3: fan protocols out to all three supported AI coding tools.
154
+ // Each tool reads project-level commands from its own path; we install
155
+ // to all three by default so users on any of Claude Code / OpenCode /
156
+ // Codex CLI get working slash commands without extra configuration.
157
+ for (const protocolFile of PROTOCOL_FILES) {
158
+ const slug = path.basename(protocolFile, '.md');
159
+ const body = await readFile(path.join(TEMPLATES_ROOT, 'protocols', protocolFile), 'utf8');
160
+ for (const tool of Object.values(TOOLS)) {
161
+ const dest = path.join(cwd, tool.destPath(slug));
162
+ const existed = existsSync(dest);
163
+ await mkdir(path.dirname(dest), { recursive: true });
164
+ await writeFile(dest, tool.render(slug, body), 'utf8');
165
+ log_line(` · ${existed ? 'overwrote' : 'installed'} ${tool.destPath(slug)} (${tool.label})`);
166
+ }
118
167
  }
119
168
  // Step 4: CLAUDE.md
120
169
  const claudeMdPath = path.join(cwd, 'CLAUDE.md');
@@ -159,26 +208,26 @@ export async function cmdInstallProtocols(prompt, options = {}) {
159
208
  exitCode: 1,
160
209
  };
161
210
  }
162
- const claudeCommandsDir = path.join(cwd, '.claude', 'commands');
163
- await mkdir(claudeCommandsDir, { recursive: true });
164
211
  const lines = [];
165
- for (const protocol of PROTOCOL_FILES) {
166
- const src = path.join(TEMPLATES_ROOT, 'protocols', protocol);
167
- const dest = path.join(claudeCommandsDir, protocol);
168
- let action = 'created';
169
- if (existsSync(dest)) {
170
- const [srcContent, destContent] = await Promise.all([
171
- readFile(src, 'utf8'),
172
- readFile(dest, 'utf8'),
173
- ]);
174
- action = srcContent === destContent ? 'unchanged' : 'updated';
175
- }
176
- if (action !== 'unchanged') {
177
- await copyFile(src, dest);
212
+ for (const protocolFile of PROTOCOL_FILES) {
213
+ const slug = path.basename(protocolFile, '.md');
214
+ const body = await readFile(path.join(TEMPLATES_ROOT, 'protocols', protocolFile), 'utf8');
215
+ for (const tool of Object.values(TOOLS)) {
216
+ const dest = path.join(cwd, tool.destPath(slug));
217
+ const rendered = tool.render(slug, body);
218
+ let action = 'created';
219
+ if (existsSync(dest)) {
220
+ const destContent = await readFile(dest, 'utf8');
221
+ action = destContent === rendered ? 'unchanged' : 'updated';
222
+ }
223
+ if (action !== 'unchanged') {
224
+ await mkdir(path.dirname(dest), { recursive: true });
225
+ await writeFile(dest, rendered, 'utf8');
226
+ }
227
+ lines.push(` ${action.padEnd(10)} ${tool.destPath(slug)}`);
178
228
  }
179
- lines.push(` ${action.padEnd(10)} .claude/commands/${protocol}`);
180
229
  }
181
- prompt.print(`Refreshing protocols at ${claudeCommandsDir}:`);
230
+ prompt.print(`Refreshing protocols (Claude Code / OpenCode / Codex CLI):`);
182
231
  for (const l of lines)
183
232
  prompt.print(l);
184
233
  return { output: '', exitCode: 0 };
@@ -1,10 +1,16 @@
1
1
  /**
2
2
  * Ollama embedder — local inference, no network egress.
3
3
  *
4
- * Default model: qwen3-embedding:8b (4096 dims, 32k context). Config via
5
- * NUOS_CATALOGUE_OLLAMA_MODEL. Smaller variants (qwen3-embedding:4b,
6
- * qwen3-embedding:0.6b) work the same way; switching variants requires
7
- * a full reindex if the dimension changes.
4
+ * Default model: qwen3-embedding:0.6b (1024 dims). Picked as default
5
+ * because it runs on the broad majority of developer machines without
6
+ * meaningful CPU strain — the prior 8b default produced noticeable load
7
+ * on Apple Silicon during a catalogue reindex, and the build harness
8
+ * ships to projects whose maintainers won't necessarily have an
9
+ * M-series Mac. Higher-fidelity variants (qwen3-embedding:4b at 2560
10
+ * dims, qwen3-embedding:8b at 4096 dims) are available via
11
+ * NUOS_CATALOGUE_OLLAMA_MODEL when the user wants better recall and
12
+ * has the headroom. Switching variants requires a full reindex because
13
+ * dimensions change.
8
14
  *
9
15
  * Why local: keeps the catalogue's content (and any future workload that
10
16
  * uses the same Embedder interface) inside whatever boundary Ollama is
@@ -26,10 +32,10 @@
26
32
  * idle-timeout (the keep_alive: "1m" we sent) cleans up within a
27
33
  * minute.
28
34
  *
29
- * Sizing note — the 8b model at Q4_K_M is ~4.7GB on disk and benefits
30
- * from ~16GB of RAM. Apple Silicon Metal acceleration helps a lot. On
31
- * smaller boxes drop to qwen3-embedding:4b (better accuracy/RAM ratio)
32
- * or qwen3-embedding:0.6b (CPU-only friendly).
35
+ * Sizing note — the new 0.6b default is ~600MB on disk and runs
36
+ * comfortably on any modern laptop, including CPU-only. The 4b variant
37
+ * (~2.5GB) and 8b variant (~4.7GB, benefits from ~16GB RAM + Metal)
38
+ * are upgrades for users who want better recall and have the headroom.
33
39
  */
34
40
  import type { Embedder } from './types.js';
35
41
  export declare class OllamaEmbedder implements Embedder {
@@ -1,10 +1,16 @@
1
1
  /**
2
2
  * Ollama embedder — local inference, no network egress.
3
3
  *
4
- * Default model: qwen3-embedding:8b (4096 dims, 32k context). Config via
5
- * NUOS_CATALOGUE_OLLAMA_MODEL. Smaller variants (qwen3-embedding:4b,
6
- * qwen3-embedding:0.6b) work the same way; switching variants requires
7
- * a full reindex if the dimension changes.
4
+ * Default model: qwen3-embedding:0.6b (1024 dims). Picked as default
5
+ * because it runs on the broad majority of developer machines without
6
+ * meaningful CPU strain — the prior 8b default produced noticeable load
7
+ * on Apple Silicon during a catalogue reindex, and the build harness
8
+ * ships to projects whose maintainers won't necessarily have an
9
+ * M-series Mac. Higher-fidelity variants (qwen3-embedding:4b at 2560
10
+ * dims, qwen3-embedding:8b at 4096 dims) are available via
11
+ * NUOS_CATALOGUE_OLLAMA_MODEL when the user wants better recall and
12
+ * has the headroom. Switching variants requires a full reindex because
13
+ * dimensions change.
8
14
  *
9
15
  * Why local: keeps the catalogue's content (and any future workload that
10
16
  * uses the same Embedder interface) inside whatever boundary Ollama is
@@ -26,12 +32,12 @@
26
32
  * idle-timeout (the keep_alive: "1m" we sent) cleans up within a
27
33
  * minute.
28
34
  *
29
- * Sizing note — the 8b model at Q4_K_M is ~4.7GB on disk and benefits
30
- * from ~16GB of RAM. Apple Silicon Metal acceleration helps a lot. On
31
- * smaller boxes drop to qwen3-embedding:4b (better accuracy/RAM ratio)
32
- * or qwen3-embedding:0.6b (CPU-only friendly).
35
+ * Sizing note — the new 0.6b default is ~600MB on disk and runs
36
+ * comfortably on any modern laptop, including CPU-only. The 4b variant
37
+ * (~2.5GB) and 8b variant (~4.7GB, benefits from ~16GB RAM + Metal)
38
+ * are upgrades for users who want better recall and have the headroom.
33
39
  */
34
- const DEFAULT_MODEL = 'qwen3-embedding:8b';
40
+ const DEFAULT_MODEL = 'qwen3-embedding:0.6b';
35
41
  const DEFAULT_HOST = 'http://localhost:11434';
36
42
  // Qwen3-Embedding produces Matryoshka representations 32–4096 dims.
37
43
  // We use the model default. A future tweak could truncate to e.g. 1024
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nusoft/nuos-build-catalogue",
3
- "version": "0.10.2",
3
+ "version": "0.12.0",
4
4
  "description": "NuOS build-catalogue tooling: semantic search (WU 110) + migration runner that lifts markdown artefacts into JSON-backed workflow records (WU 111, Phase G).",
5
5
  "type": "module",
6
6
  "bin": {