nexarch 0.12.29 → 0.12.33

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.
@@ -6,8 +6,22 @@ import { saveCredentials } from "../lib/credentials.js";
6
6
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
7
7
  import { homedir } from "os";
8
8
  import { join } from "path";
9
+ import { selectFromList } from "../lib/prompt-select.js";
9
10
  const NEXARCH_URL = "https://nexarch.ai";
10
11
  const LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
12
+ // Mirrors AGENT_CAPABILITY_PROFILES in web/src/lib/mcp-agents.ts — the
13
+ // server is the actual authority on what each profile grants; this is just
14
+ // the same labels/descriptions shown in the enrollment UI, reused here so
15
+ // the CLI prompt and the web form agree about what "Worker" means.
16
+ const PROFILE_CHOICES = [
17
+ { value: "observer", label: "Observer", description: "Read graph, policies, and governance context. Cannot write or claim work." },
18
+ { value: "contributor", label: "Contributor", description: "Observer, plus proposing entities/relationships and new applications." },
19
+ { value: "worker", label: "Worker", description: "Contributor, plus checking in, claiming, and completing commands." },
20
+ { value: "delivery", label: "Delivery", description: "Worker, plus activating proposed applications." },
21
+ ];
22
+ async function promptCapabilityProfile() {
23
+ return selectFromList("Select a capability profile for this credential:", PROFILE_CHOICES, "worker");
24
+ }
11
25
  function printLoginBanner() {
12
26
  const logo = String.raw `
13
27
  ###### ######
@@ -136,10 +150,14 @@ export async function login(args) {
136
150
  const state = generateState();
137
151
  const port = await findFreePort();
138
152
  const requestedCompany = getArgValue(args, "--company");
139
- const qp = new URLSearchParams({ port: String(port), state });
153
+ const requestedProfile = getArgValue(args, "--profile");
154
+ const isCapabilityProfile = (value) => PROFILE_CHOICES.some((c) => c.value === value);
155
+ const profile = isCapabilityProfile(requestedProfile) ? requestedProfile : await promptCapabilityProfile();
156
+ const qp = new URLSearchParams({ port: String(port), state, profile });
140
157
  if (requestedCompany)
141
158
  qp.set("company", requestedCompany);
142
159
  const authUrl = `${NEXARCH_URL}/auth/cli?${qp.toString()}`;
160
+ console.log(`\nCapability profile: ${profile}`);
143
161
  console.log("Opening Nexarch in your browser…");
144
162
  console.log(`\n ${authUrl}\n`);
145
163
  console.log("If the browser did not open, copy the URL above and paste it in manually.\n");
@@ -3,7 +3,7 @@ import { requireCredentials } from "../lib/credentials.js";
3
3
  import { detectClientsFromRegistry, writeClientConfig, nexarchServerBlockFromRegistry } from "../lib/clients.js";
4
4
  import { fetchAgentRegistryOrThrow } from "../lib/agent-registry.js";
5
5
  import { initAgent } from "./init-agent.js";
6
- import { installClaudeCodeSkill } from "../lib/skills.js";
6
+ import { installSkillsForRuntime } from "../lib/skills.js";
7
7
  import { login } from "./login.js";
8
8
  function argValue(args, flag) {
9
9
  const idx = args.indexOf(flag);
@@ -168,25 +168,38 @@ export async function setup(args) {
168
168
  initAgentArgs.push("--instruction-runtime-targets", instructionRuntimeTargets.join(","));
169
169
  }
170
170
  await initAgent(initAgentArgs);
171
- // Claude Code supports Agent Skills: a trigger description that sits
172
- // permanently in the agent's context and loads a playbook when a build-shaped
173
- // moment matches. MCP alone makes the graph available; the skill is what
174
- // makes an agent check it before building something new. Installed under the
175
- // same consent as instruction writes setup already opts into those above.
176
- const hasClaudeCode = clients.some((client) => client.code === "claude-code");
177
- if (hasClaudeCode) {
171
+ // Claude Code and Codex CLI both support Agent Skills: a trigger
172
+ // description that sits permanently in the agent's context and loads a
173
+ // playbook when a matching moment comes up. MCP alone makes the graph
174
+ // available; skills are what make an agent actually consult it for
175
+ // building, diagramming, working its command queue, and governance
176
+ // review. Both runtimes use the same SKILL.md format, just a different
177
+ // root directory (see RUNTIME_SKILLS_ROOT in lib/skills.ts), so the same
178
+ // content installs to both unmodified. Installed under the same consent
179
+ // as instruction writes — setup already opts into those above.
180
+ const skillRuntimes = [
181
+ { clientCode: "claude-code", runtime: "claude-code", label: "Claude Code" },
182
+ { clientCode: "codex-cli", runtime: "codex-cli", label: "Codex CLI" },
183
+ ];
184
+ for (const { clientCode, runtime, label } of skillRuntimes) {
185
+ if (!clients.some((client) => client.code === clientCode))
186
+ continue;
178
187
  try {
179
- const skill = installClaudeCodeSkill(registry);
180
- const verb = skill.status === "installed" ? "installed" : skill.status === "updated" ? "updated" : "already current";
181
- console.log(`\nClaude Code skill ${verb}: ${skill.path}`);
182
- if (skill.status !== "already_current") {
183
- console.log(" New Claude Code sessions will check the architecture graph before building something new.");
188
+ const skills = installSkillsForRuntime(registry, runtime);
189
+ const anyChanged = skills.some((s) => s.status !== "already_current");
190
+ console.log(`\n${label} skills:`);
191
+ for (const skill of skills) {
192
+ const verb = skill.status === "installed" ? "installed" : skill.status === "updated" ? "updated" : "already current";
193
+ console.log(` ${verb.padEnd(15)} ${skill.dirName}`);
194
+ }
195
+ if (anyChanged) {
196
+ console.log(` New ${label} sessions will use these to decide when to consult Nexarch.`);
184
197
  }
185
198
  }
186
199
  catch (err) {
187
200
  const message = err instanceof Error ? err.message : String(err);
188
- console.log(`\nClaude Code skill install failed — ${message}`);
189
- console.log(" Setup is otherwise complete; re-run setup to retry the skill.");
201
+ console.log(`\n${label} skill install failed — ${message}`);
202
+ console.log(" Setup is otherwise complete; re-run setup to retry the skills.");
190
203
  }
191
204
  }
192
205
  if (clients.length > 0) {
@@ -0,0 +1,72 @@
1
+ import readline from "readline";
2
+ /**
3
+ * Arrow-key single-select prompt. No dependency — this package has none for
4
+ * a reason (see package.json), and Node's own readline keypress events cover
5
+ * the whole interaction: up/down to move, enter to confirm, ctrl+c to abort.
6
+ *
7
+ * Falls back to `defaultValue` without prompting when stdin/stdout isn't a
8
+ * TTY (piped input, --non-interactive runs, CI) — the same guard setup.ts
9
+ * already uses for its yes/no prompt.
10
+ */
11
+ export async function selectFromList(title, choices, defaultValue) {
12
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
13
+ return defaultValue;
14
+ }
15
+ const defaultIndex = choices.findIndex((c) => c.value === defaultValue);
16
+ let index = defaultIndex >= 0 ? defaultIndex : 0;
17
+ let linesDrawn = 0;
18
+ const render = () => {
19
+ if (linesDrawn > 0) {
20
+ readline.moveCursor(process.stdout, 0, -linesDrawn);
21
+ readline.cursorTo(process.stdout, 0);
22
+ readline.clearScreenDown(process.stdout);
23
+ }
24
+ const lines = [
25
+ `${title} (↑/↓ to move, enter to select)`,
26
+ ...choices.flatMap((choice, i) => {
27
+ const selected = i === index;
28
+ const pointer = selected ? "❯ " : " ";
29
+ const label = selected ? `\x1b[1m${choice.label}\x1b[0m` : choice.label;
30
+ return [`${pointer}${label}`, ` ${choice.description}`];
31
+ }),
32
+ ];
33
+ process.stdout.write(lines.join("\n") + "\n");
34
+ linesDrawn = lines.length;
35
+ };
36
+ return new Promise((resolve) => {
37
+ readline.emitKeypressEvents(process.stdin);
38
+ const wasRaw = process.stdin.isRaw;
39
+ process.stdin.setRawMode(true);
40
+ process.stdin.resume();
41
+ const cleanup = () => {
42
+ process.stdin.removeListener("keypress", onKeypress);
43
+ process.stdin.setRawMode(Boolean(wasRaw));
44
+ process.stdin.pause();
45
+ };
46
+ const onKeypress = (_str, key) => {
47
+ if (!key)
48
+ return;
49
+ if (key.ctrl && key.name === "c") {
50
+ cleanup();
51
+ console.log();
52
+ process.exit(130);
53
+ }
54
+ if (key.name === "up") {
55
+ index = (index - 1 + choices.length) % choices.length;
56
+ render();
57
+ return;
58
+ }
59
+ if (key.name === "down") {
60
+ index = (index + 1) % choices.length;
61
+ render();
62
+ return;
63
+ }
64
+ if (key.name === "return") {
65
+ cleanup();
66
+ resolve(choices[index].value);
67
+ }
68
+ };
69
+ render();
70
+ process.stdin.on("keypress", onKeypress);
71
+ });
72
+ }
@@ -1,24 +1,7 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
2
2
  import { homedir } from "os";
3
3
  import { join } from "path";
4
- /**
5
- * Claude Code skill installation.
6
- *
7
- * MCP makes the architecture graph *available* to an agent; nothing makes the
8
- * agent *consult* it. Tool descriptions are skimmed once and forgotten, and the
9
- * injected AGENTS.md block is repo-scoped. A skill closes that gap: its
10
- * description sits permanently in the agent's context as a trigger, and when a
11
- * build-shaped moment matches, the full playbook loads.
12
- *
13
- * The body is registry-managed (template nexarch_claude_code_skill_v1) so it
14
- * can be updated by migration like the instruction templates; the baked-in
15
- * fallback below keeps installs working when the registry predates the
16
- * template. Installed to the user-level skills directory because agent setup is
17
- * a per-machine action, like MCP client configuration.
18
- */
19
- export const CLAUDE_SKILL_TEMPLATE_CODE = "nexarch_claude_code_skill_v1";
20
- export const CLAUDE_SKILL_DIR_NAME = "nexarch-architecture-graph";
21
- export const FALLBACK_SKILL_BODY = `---
4
+ const ARCHITECTURE_GRAPH_SKILL_BODY = `---
22
5
  name: nexarch-architecture-graph
23
6
  description: Consult the organisation's Nexarch architecture graph before building anything new, and register what gets built. Use when creating a new service, application, module, integration, API endpoint, or scheduled job; when adding a significant dependency or choosing between libraries; when asked whether a capability, integration, or dataset already exists; or when asked what applications or systems the organisation has. Requires the Nexarch MCP tools (nexarch_*).
24
7
  ---
@@ -61,21 +44,282 @@ repository; the graph knows all of them.
61
44
  - End architectural work with a one-line summary of what was recorded and what
62
45
  remains unresolved.
63
46
  `;
64
- export function installClaudeCodeSkill(registry, options = {}) {
65
- const template = registry.instructionTemplates.find((t) => t.code === CLAUDE_SKILL_TEMPLATE_CODE);
66
- const body = template ? template.body.trim() + "\n" : FALLBACK_SKILL_BODY;
47
+ const DIAGRAM_VIEWS_SKILL_BODY = `---
48
+ name: nexarch-diagram-views
49
+ description: Create, update, or find an architecture diagram in Nexarch. Use when asked to visualize, diagram, or map the architecture; show a system topology or environment overview; draw how something connects to the rest of the estate; or update an existing diagram's layout or filters. Requires the Nexarch MCP tools (nexarch_*).
50
+ ---
51
+
52
+ # Nexarch Diagram Views
53
+
54
+ Diagrams in Nexarch are saved views over the live graph, not static images —
55
+ each one has filters, a layout, and a revision history, and every view is
56
+ shared workspace state, not scratch output.
57
+
58
+ ## Before creating one
59
+
60
+ 1. \`nexarch_list_diagram_views\` — check whether a view covering this already
61
+ exists (they're grouped by folder). Reuse an existing view over creating a
62
+ near-duplicate; the same reuse-before-build principle applies to diagrams
63
+ as it does to applications.
64
+ 2. If one exists but looks stale or wrong, \`nexarch_get_diagram_view\` to see
65
+ its current filters/layout before deciding whether to update it or start
66
+ fresh.
67
+
68
+ ## Creating or refreshing a view
69
+
70
+ - Prefer \`nexarch_generate_diagram_view\` over hand-building one: it derives
71
+ nodes and edges straight from graph semantics (\`application_topology\`,
72
+ \`environment_overview\`, or \`edge_to_runtime\`), so it's correct by
73
+ construction and stays correct as the graph changes.
74
+ - Reach for \`nexarch_create_diagram_view\` only when you need a specific,
75
+ hand-picked set of filters that generation doesn't produce.
76
+ - To modify an existing view's filters or layout, \`nexarch_update_diagram_view\`
77
+ is optimistic-locked — always \`nexarch_get_diagram_view\` first to read the
78
+ current \`lockVersion\` and pass it back, or the update will be rejected
79
+ rather than silently overwriting a concurrent edit.
80
+
81
+ ## Ground rules
82
+
83
+ - Never write a diagram view no one asked for; a rendered summary in chat is
84
+ enough unless the human wants it saved to the workspace.
85
+ - \`nexarch_list_diagram_revisions\` before assuming a view's current state is
86
+ its only state — check history before concluding something was never
87
+ captured.
88
+ - Confirm which view you updated (name, folder) so the human can find it in
89
+ the workspace, not just in this conversation.
90
+ `;
91
+ const AGENT_WORKQUEUE_SKILL_BODY = `---
92
+ name: nexarch-agent-workqueue
93
+ description: Check for and work through pending Nexarch commands assigned to this agent. Use when the human asks you to check in, look for pending work, process the command queue, or asks whether there's anything waiting for this agent. Requires the Nexarch MCP tools (nexarch_*).
94
+ ---
95
+
96
+ # Nexarch Agent Work Queue
97
+
98
+ Nexarch can queue commands for a specific registered agent — review requests,
99
+ scans, follow-ups raised by other agents or humans in the workspace. This
100
+ skill is the check-in → claim → complete loop for working that queue.
101
+
102
+ ## Checking in
103
+
104
+ - \`nexarch_check_in\` previews pending commands and draft/proposed
105
+ applications for this agent. It does **not** claim or change anything —
106
+ safe to call any time, including proactively at the start of a session.
107
+ - Report exactly what check-in found. Don't reinterpret "check in" as
108
+ registration (\`init-agent\`) or as a general health check
109
+ (\`nexarch_get_governance_summary\`) — they're different actions.
110
+ - If check-in fails because this agent isn't registered yet, that's a signal
111
+ to register first, not to fall back to a different tool.
112
+
113
+ ## Working a command
114
+
115
+ 1. Only claim a specific command with \`nexarch_claim_command_by_id\` when the
116
+ human explicitly wants it worked — check-in surfacing a command is not
117
+ itself permission to claim it.
118
+ 2. Do the work the command describes.
119
+ 3. Close it out: \`nexarch_complete_command\` on success, or
120
+ \`nexarch_fail_command\` with a reason if it couldn't be done. Never leave a
121
+ claimed command unresolved at the end of a session.
122
+
123
+ ## If check-in shows a command you can't claim
124
+
125
+ Claiming/completing/failing a command needs the \`mcp:work:commands\` scope —
126
+ a separate, higher capability profile ("worker" or above) than what does the
127
+ graph writing (\`mcp:write:discovery\`, granted from "contributor" up). A
128
+ credential can see pending commands and still lack the scope to claim one;
129
+ check-in's response says so explicitly when that's the case
130
+ (\`claimReason: "credential_missing_work_scope"\`). If you hit this: still do
131
+ the work if it's clearly what's needed, but say in your summary that you
132
+ couldn't formally claim/complete the command, and that a human needs to
133
+ either handle it in the workspace or reissue your credential with the
134
+ "worker" profile. Don't call \`nexarch_claim_command_by_id\` speculatively —
135
+ if your tools/list doesn't include it, your credential doesn't have the
136
+ scope, and the call will fail.
137
+
138
+ ## Ground rules
139
+
140
+ - \`nexarch_claim_command\` is a legacy alias for \`nexarch_check_in\` — use
141
+ \`nexarch_check_in\` directly, not the alias.
142
+ - One command at a time is fine; don't claim ahead of what you can actually
143
+ finish in this session.
144
+ - Summarize what was claimed, completed, or failed — the human wasn't
145
+ watching the queue directly.
146
+ `;
147
+ const GOVERNANCE_REVIEW_SKILL_BODY = `---
148
+ name: nexarch-governance-review
149
+ description: Check or record compliance, policy audit results, decision conformance, or review a proposed application before activation. Use when asked about compliance or policy status for an app, to run or record a policy audit, whether an architectural decision was actually implemented, or to review/activate an application waiting in the proposed queue. Requires the Nexarch MCP tools (nexarch_*).
150
+ ---
151
+
152
+ # Nexarch Governance Review
153
+
154
+ Governance in Nexarch is evidence-backed: controls are assigned to entities,
155
+ audits are recorded (not inferred), and proposed applications wait for
156
+ explicit human activation. This skill covers reading and recording that
157
+ evidence — never invent a compliance status that isn't stored.
158
+
159
+ ## Compliance and policy status
160
+
161
+ - Asked about compliance or policy status for an app → \`nexarch_get_policy_audit_results\`
162
+ first. **No stored results means say so** — "no audit has been run" — rather
163
+ than guessing from the code.
164
+ - \`nexarch_get_entity_policy_controls\` to see which controls actually apply
165
+ to a given entity before assessing it against the wrong set.
166
+ - To record a real audit: read the applicable rules from
167
+ \`nexarch_get_applied_policies\`, evaluate the evidence yourself, then
168
+ persist the findings with \`nexarch_submit_policy_audit\`. The tool stores
169
+ results — it does not evaluate anything for you.
170
+ - \`nexarch_submit_decision_conformance\` records whether a repository actually
171
+ implements a \`decision_record\` at a given commit, with evidence. Use this
172
+ when checking whether an architectural decision was followed, not just
173
+ whether it was written down.
174
+
175
+ ## Proposed applications
176
+
177
+ - \`nexarch_list_proposed_applications\` — the review queue: apps an agent
178
+ drafted that are waiting on a human.
179
+ - \`nexarch_get_proposed_application\` — full context before recommending
180
+ anything: recommendation provenance, capability gaps, linked policy
181
+ controls, pre-scaffold review requirements.
182
+ - \`nexarch_activate_proposed_application\` moves one to active state. Only
183
+ call this once the human has actually asked for it and the scaffolding is
184
+ real — being listed as proposed is not itself permission to activate.
185
+
186
+ ## Ground rules
187
+
188
+ - Findings are evidence, not opinion: cite what \`nexarch_get_policy_audit_results\`
189
+ or \`nexarch_submit_policy_audit\` actually returned rather than paraphrasing
190
+ from memory.
191
+ - Never activate a proposed application on your own judgement alone — surface
192
+ what you found and let the human decide, same as the reuse-before-build
193
+ principle elsewhere in Nexarch.
194
+ `;
195
+ const DECISION_RECORDS_SKILL_BODY = `---
196
+ name: nexarch-decision-records
197
+ description: Record an architectural decision in Nexarch, or check what's already been decided about something. Use when a decision is being made (a technology, pattern, or approach chosen over alternatives), when ADR/RFC documents are found in a repository, when asked what's already been decided about a topic, or when a new decision replaces an older one. Requires the Nexarch MCP tools (nexarch_*).
198
+ ---
199
+
200
+ # Nexarch Decision Records
201
+
202
+ Decisions are first-class in Nexarch: \`decision_record\` entities linked to
203
+ whatever they concern via \`decides\`, reviewed in the workspace's Decisions
204
+ page, and optionally promoted into workspace-wide policy by a human. This
205
+ skill is for recording one as it happens — mining a whole repository's ADR
206
+ history systematically is a separate, human-triggered Decision Review
207
+ command from the application's page in the workspace.
208
+
209
+ ## Before recording
210
+
211
+ \`nexarch_resolve_reference\` / \`nexarch_list_entities\` for an existing
212
+ \`decision_record\` covering the same choice — match by name/summary first.
213
+ Reuse and refresh it rather than creating a duplicate for the same decision.
214
+
215
+ ## Recording a decision
216
+
217
+ \`entityTypeCode: "decision_record"\`. Two attributes are strictly required —
218
+ MCP rejects the write with \`INVALID_DECISION_RECORD_PAYLOAD\` if either is
219
+ missing or empty, with no fallback to \`description\` (ADR-0101):
220
+
221
+ - \`attributes.decision.summary\` — one sentence naming what was chosen.
222
+ - \`attributes.decision.detail\` — the fuller rationale, cited to its evidence.
223
+
224
+ Never invent a decision from a technology's mere presence — it must trace to
225
+ an actual stated reason someone chose it over an alternative.
226
+
227
+ ### Optional fields — add them when the evidence actually supports them
228
+
229
+ All additive; a decision recorded with only summary/detail is still
230
+ completely valid. Leave a field unset rather than guessing it:
231
+
232
+ - \`attributes.source.repositoryUrl\` / \`.commit\` / \`.path\` / \`.lines\` — where
233
+ the decision was found, so the workspace can show exact evidence and
234
+ detect when it drifts from what's now in the repo.
235
+ - \`attributes.decision.status\` — one of \`unknown\` / \`proposed\` / \`accepted\`
236
+ / \`deprecated\` / \`superseded\` / \`rejected\`, only when the source states it
237
+ explicitly.
238
+ - \`attributes.decision.rationale\` — the "why", as its own field, when it's
239
+ distinguishable from the alternatives/consequences discussion.
240
+ - \`attributes.decision.alternatives\` — array of \`{"option": "...",
241
+ "whyRejected": "..."}\` for alternatives the source explicitly names and
242
+ explains rejecting. Don't invent alternatives the source never mentions.
243
+ - \`attributes.decision.consequences\` — array of plain-string trade-offs the
244
+ source explicitly states.
245
+ - \`attributes.decision.supersededByRef\` — the \`entityRef\` of the
246
+ \`decision_record\` that replaces this one, only when the source explicitly
247
+ says so. Setting this on an upsert automatically flags the old decision as
248
+ superseded in the workspace — you don't need to touch the old record
249
+ yourself.
250
+
251
+ ### Linking
252
+
253
+ \`decision_record -> decides -> <application | technology_component |
254
+ platform | api | other ontology-valid target>\` — the most specific existing
255
+ entity the decision actually concerns. Only link to entities that already
256
+ exist; this skill records decisions, it doesn't discover architecture. If
257
+ nothing ontology-valid exists yet to link to, still record the decision and
258
+ say so rather than skipping it or inventing a target.
259
+
260
+ ## Ground rules
261
+
262
+ - A decision being implemented is not the same as a decision being followed
263
+ correctly — recording one here says nothing about whether the code
264
+ actually conforms. That's \`nexarch_submit_decision_conformance\` (see the
265
+ \`nexarch-governance-review\` skill), a separate, evidence-based check.
266
+ - Conflicts, duplicates, and workspace-wide promotion of a decision are
267
+ reviewed by a human in the workspace UI, not something to resolve or
268
+ decide yourself — your job is accurate recording and evidenced linking.
269
+ - End with a one-line summary: what was recorded or reused, and what (if
270
+ anything) couldn't be linked.
271
+ `;
272
+ export const SKILLS = [
273
+ {
274
+ templateCode: "nexarch_claude_code_skill_v1",
275
+ dirName: "nexarch-architecture-graph",
276
+ fallbackBody: ARCHITECTURE_GRAPH_SKILL_BODY,
277
+ },
278
+ {
279
+ templateCode: "nexarch_diagram_views_skill_v1",
280
+ dirName: "nexarch-diagram-views",
281
+ fallbackBody: DIAGRAM_VIEWS_SKILL_BODY,
282
+ },
283
+ {
284
+ templateCode: "nexarch_agent_workqueue_skill_v1",
285
+ dirName: "nexarch-agent-workqueue",
286
+ fallbackBody: AGENT_WORKQUEUE_SKILL_BODY,
287
+ },
288
+ {
289
+ templateCode: "nexarch_governance_review_skill_v1",
290
+ dirName: "nexarch-governance-review",
291
+ fallbackBody: GOVERNANCE_REVIEW_SKILL_BODY,
292
+ },
293
+ {
294
+ templateCode: "nexarch_decision_records_skill_v1",
295
+ dirName: "nexarch-decision-records",
296
+ fallbackBody: DECISION_RECORDS_SKILL_BODY,
297
+ },
298
+ ];
299
+ const RUNTIME_SKILLS_ROOT = {
300
+ "claude-code": [".claude", "skills"],
301
+ "codex-cli": [".agents", "skills"],
302
+ };
303
+ function installSkill(spec, registry, skillsRoot) {
304
+ const template = registry.instructionTemplates.find((t) => t.code === spec.templateCode);
305
+ const body = template ? template.body.trim() + "\n" : spec.fallbackBody;
67
306
  const source = template ? "registry" : "fallback";
68
- const skillDir = join(options.homeDir ?? homedir(), ".claude", "skills", CLAUDE_SKILL_DIR_NAME);
307
+ const skillDir = join(skillsRoot, spec.dirName);
69
308
  const skillPath = join(skillDir, "SKILL.md");
70
309
  if (existsSync(skillPath)) {
71
310
  const existing = readFileSync(skillPath, "utf8");
72
311
  if (existing === body) {
73
- return { path: skillPath, status: "already_current", source };
312
+ return { dirName: spec.dirName, path: skillPath, status: "already_current", source };
74
313
  }
75
314
  writeFileSync(skillPath, body, "utf8");
76
- return { path: skillPath, status: "updated", source };
315
+ return { dirName: spec.dirName, path: skillPath, status: "updated", source };
77
316
  }
78
317
  mkdirSync(skillDir, { recursive: true });
79
318
  writeFileSync(skillPath, body, "utf8");
80
- return { path: skillPath, status: "installed", source };
319
+ return { dirName: spec.dirName, path: skillPath, status: "installed", source };
320
+ }
321
+ export function installSkillsForRuntime(registry, runtime, options = {}) {
322
+ const homeDir = options.homeDir ?? homedir();
323
+ const skillsRoot = join(homeDir, ...RUNTIME_SKILLS_ROOT[runtime]);
324
+ return SKILLS.map((spec) => installSkill(spec, registry, skillsRoot));
81
325
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexarch",
3
- "version": "0.12.29",
3
+ "version": "0.12.33",
4
4
  "description": "Your architecture workspace for AI delivery.",
5
5
  "keywords": [
6
6
  "nexarch",