nexarch 0.12.31 → 0.12.34
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/dist/commands/login.js +19 -1
- package/dist/lib/prompt-select.js +72 -0
- package/dist/lib/skills.js +122 -4
- package/package.json +1 -1
package/dist/commands/login.js
CHANGED
|
@@ -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
|
|
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");
|
|
@@ -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
|
+
}
|
package/dist/lib/skills.js
CHANGED
|
@@ -104,6 +104,18 @@ skill is the check-in → claim → complete loop for working that queue.
|
|
|
104
104
|
- \`nexarch_check_in\` previews pending commands and draft/proposed
|
|
105
105
|
applications for this agent. It does **not** claim or change anything —
|
|
106
106
|
safe to call any time, including proactively at the start of a session.
|
|
107
|
+
- If you already know which application you're operating in — you're
|
|
108
|
+
working inside its repo, or it was named earlier in this conversation —
|
|
109
|
+
pass it as \`applicationContext: { accessibleApplicationRefs: ["application:<ref>"] }\`
|
|
110
|
+
on this **first** call. Without it, every command comes back
|
|
111
|
+
\`claimable: false\` (\`claimReason: "application_context_required_for_claim"\`)
|
|
112
|
+
even if you could otherwise claim it, and you'll need a second check-in
|
|
113
|
+
call with that same ref before \`nexarch_claim_command_by_id\` will work —
|
|
114
|
+
a wasted round trip when the application was knowable up front.
|
|
115
|
+
- If you don't yet know the application, call check-in bare first. Each
|
|
116
|
+
returned command carries \`target_entity_key\` (e.g.
|
|
117
|
+
\`"application:veri_viva_platform_website"\`) — call check-in again with
|
|
118
|
+
that as \`accessibleApplicationRefs\` before trying to claim it.
|
|
107
119
|
- Report exactly what check-in found. Don't reinterpret "check in" as
|
|
108
120
|
registration (\`init-agent\`) or as a general health check
|
|
109
121
|
(\`nexarch_get_governance_summary\`) — they're different actions.
|
|
@@ -112,13 +124,37 @@ skill is the check-in → claim → complete loop for working that queue.
|
|
|
112
124
|
|
|
113
125
|
## Working a command
|
|
114
126
|
|
|
127
|
+
Claiming is a commitment, not a checkbox. Don't claim a command unless
|
|
128
|
+
you're about to do the three steps below in the same turn — a claimed
|
|
129
|
+
command that's never executed or closed out sits there looking done while
|
|
130
|
+
blocking that queue slot, which is worse than never having claimed it.
|
|
131
|
+
|
|
115
132
|
1. Only claim a specific command with \`nexarch_claim_command_by_id\` when the
|
|
116
133
|
human explicitly wants it worked — check-in surfacing a command is not
|
|
117
|
-
itself permission to claim it
|
|
118
|
-
|
|
134
|
+
itself permission to claim it, and neither is check-in itself: claiming
|
|
135
|
+
is a separate, deliberate call you make because you're about to act.
|
|
136
|
+
2. **Execute \`command.resolved_playbook_text\` from the claim response** —
|
|
137
|
+
that field is the actual task. \`command.instructions\` is a different
|
|
138
|
+
field and is almost always \`null\`; don't mistake its emptiness for "no
|
|
139
|
+
work to do."
|
|
119
140
|
3. Close it out: \`nexarch_complete_command\` on success, or
|
|
120
|
-
\`nexarch_fail_command\` with a reason if it couldn't be done. Never
|
|
121
|
-
|
|
141
|
+
\`nexarch_fail_command\` with a reason if it couldn't be done. Never end a
|
|
142
|
+
turn with a command still claimed and nothing else called.
|
|
143
|
+
|
|
144
|
+
## If check-in shows a command you can't claim
|
|
145
|
+
|
|
146
|
+
Claiming/completing/failing a command needs the \`mcp:work:commands\` scope —
|
|
147
|
+
a separate, higher capability profile ("worker" or above) than what does the
|
|
148
|
+
graph writing (\`mcp:write:discovery\`, granted from "contributor" up). A
|
|
149
|
+
credential can see pending commands and still lack the scope to claim one;
|
|
150
|
+
check-in's response says so explicitly when that's the case
|
|
151
|
+
(\`claimReason: "credential_missing_work_scope"\`). If you hit this: still do
|
|
152
|
+
the work if it's clearly what's needed, but say in your summary that you
|
|
153
|
+
couldn't formally claim/complete the command, and that a human needs to
|
|
154
|
+
either handle it in the workspace or reissue your credential with the
|
|
155
|
+
"worker" profile. Don't call \`nexarch_claim_command_by_id\` speculatively —
|
|
156
|
+
if your tools/list doesn't include it, your credential doesn't have the
|
|
157
|
+
scope, and the call will fail.
|
|
122
158
|
|
|
123
159
|
## Ground rules
|
|
124
160
|
|
|
@@ -177,6 +213,83 @@ evidence — never invent a compliance status that isn't stored.
|
|
|
177
213
|
what you found and let the human decide, same as the reuse-before-build
|
|
178
214
|
principle elsewhere in Nexarch.
|
|
179
215
|
`;
|
|
216
|
+
const DECISION_RECORDS_SKILL_BODY = `---
|
|
217
|
+
name: nexarch-decision-records
|
|
218
|
+
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_*).
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
# Nexarch Decision Records
|
|
222
|
+
|
|
223
|
+
Decisions are first-class in Nexarch: \`decision_record\` entities linked to
|
|
224
|
+
whatever they concern via \`decides\`, reviewed in the workspace's Decisions
|
|
225
|
+
page, and optionally promoted into workspace-wide policy by a human. This
|
|
226
|
+
skill is for recording one as it happens — mining a whole repository's ADR
|
|
227
|
+
history systematically is a separate, human-triggered Decision Review
|
|
228
|
+
command from the application's page in the workspace.
|
|
229
|
+
|
|
230
|
+
## Before recording
|
|
231
|
+
|
|
232
|
+
\`nexarch_resolve_reference\` / \`nexarch_list_entities\` for an existing
|
|
233
|
+
\`decision_record\` covering the same choice — match by name/summary first.
|
|
234
|
+
Reuse and refresh it rather than creating a duplicate for the same decision.
|
|
235
|
+
|
|
236
|
+
## Recording a decision
|
|
237
|
+
|
|
238
|
+
\`entityTypeCode: "decision_record"\`. Two attributes are strictly required —
|
|
239
|
+
MCP rejects the write with \`INVALID_DECISION_RECORD_PAYLOAD\` if either is
|
|
240
|
+
missing or empty, with no fallback to \`description\` (ADR-0101):
|
|
241
|
+
|
|
242
|
+
- \`attributes.decision.summary\` — one sentence naming what was chosen.
|
|
243
|
+
- \`attributes.decision.detail\` — the fuller rationale, cited to its evidence.
|
|
244
|
+
|
|
245
|
+
Never invent a decision from a technology's mere presence — it must trace to
|
|
246
|
+
an actual stated reason someone chose it over an alternative.
|
|
247
|
+
|
|
248
|
+
### Optional fields — add them when the evidence actually supports them
|
|
249
|
+
|
|
250
|
+
All additive; a decision recorded with only summary/detail is still
|
|
251
|
+
completely valid. Leave a field unset rather than guessing it:
|
|
252
|
+
|
|
253
|
+
- \`attributes.source.repositoryUrl\` / \`.commit\` / \`.path\` / \`.lines\` — where
|
|
254
|
+
the decision was found, so the workspace can show exact evidence and
|
|
255
|
+
detect when it drifts from what's now in the repo.
|
|
256
|
+
- \`attributes.decision.status\` — one of \`unknown\` / \`proposed\` / \`accepted\`
|
|
257
|
+
/ \`deprecated\` / \`superseded\` / \`rejected\`, only when the source states it
|
|
258
|
+
explicitly.
|
|
259
|
+
- \`attributes.decision.rationale\` — the "why", as its own field, when it's
|
|
260
|
+
distinguishable from the alternatives/consequences discussion.
|
|
261
|
+
- \`attributes.decision.alternatives\` — array of \`{"option": "...",
|
|
262
|
+
"whyRejected": "..."}\` for alternatives the source explicitly names and
|
|
263
|
+
explains rejecting. Don't invent alternatives the source never mentions.
|
|
264
|
+
- \`attributes.decision.consequences\` — array of plain-string trade-offs the
|
|
265
|
+
source explicitly states.
|
|
266
|
+
- \`attributes.decision.supersededByRef\` — the \`entityRef\` of the
|
|
267
|
+
\`decision_record\` that replaces this one, only when the source explicitly
|
|
268
|
+
says so. Setting this on an upsert automatically flags the old decision as
|
|
269
|
+
superseded in the workspace — you don't need to touch the old record
|
|
270
|
+
yourself.
|
|
271
|
+
|
|
272
|
+
### Linking
|
|
273
|
+
|
|
274
|
+
\`decision_record -> decides -> <application | technology_component |
|
|
275
|
+
platform | api | other ontology-valid target>\` — the most specific existing
|
|
276
|
+
entity the decision actually concerns. Only link to entities that already
|
|
277
|
+
exist; this skill records decisions, it doesn't discover architecture. If
|
|
278
|
+
nothing ontology-valid exists yet to link to, still record the decision and
|
|
279
|
+
say so rather than skipping it or inventing a target.
|
|
280
|
+
|
|
281
|
+
## Ground rules
|
|
282
|
+
|
|
283
|
+
- A decision being implemented is not the same as a decision being followed
|
|
284
|
+
correctly — recording one here says nothing about whether the code
|
|
285
|
+
actually conforms. That's \`nexarch_submit_decision_conformance\` (see the
|
|
286
|
+
\`nexarch-governance-review\` skill), a separate, evidence-based check.
|
|
287
|
+
- Conflicts, duplicates, and workspace-wide promotion of a decision are
|
|
288
|
+
reviewed by a human in the workspace UI, not something to resolve or
|
|
289
|
+
decide yourself — your job is accurate recording and evidenced linking.
|
|
290
|
+
- End with a one-line summary: what was recorded or reused, and what (if
|
|
291
|
+
anything) couldn't be linked.
|
|
292
|
+
`;
|
|
180
293
|
export const SKILLS = [
|
|
181
294
|
{
|
|
182
295
|
templateCode: "nexarch_claude_code_skill_v1",
|
|
@@ -198,6 +311,11 @@ export const SKILLS = [
|
|
|
198
311
|
dirName: "nexarch-governance-review",
|
|
199
312
|
fallbackBody: GOVERNANCE_REVIEW_SKILL_BODY,
|
|
200
313
|
},
|
|
314
|
+
{
|
|
315
|
+
templateCode: "nexarch_decision_records_skill_v1",
|
|
316
|
+
dirName: "nexarch-decision-records",
|
|
317
|
+
fallbackBody: DECISION_RECORDS_SKILL_BODY,
|
|
318
|
+
},
|
|
201
319
|
];
|
|
202
320
|
const RUNTIME_SKILLS_ROOT = {
|
|
203
321
|
"claude-code": [".claude", "skills"],
|