@novare-technologies/nvaid 0.0.0-stage → 0.2.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.
Files changed (32) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +137 -2
  3. package/dist/cli.js +483 -0
  4. package/package.json +49 -4
  5. package/templates/commands/apply.md +36 -0
  6. package/templates/commands/breakdown.md +35 -0
  7. package/templates/commands/contracts.md +43 -0
  8. package/templates/commands/install.md +63 -0
  9. package/templates/commands/intent.md +46 -0
  10. package/templates/nvaid/DESIGN-CODE-DICTIONARY.md +90 -0
  11. package/templates/nvaid/PROJECT-RULES.md +93 -0
  12. package/templates/nvaid/config.yml +87 -0
  13. package/templates/nvaid/intents/.gitkeep +0 -0
  14. package/templates/rules/ISSUE-TRACKER-TEMPLATE.md +221 -0
  15. package/templates/rules/backend-architecture-contract-generation-rules.md +56 -0
  16. package/templates/rules/customer-experience-contract-generation-rules.md +51 -0
  17. package/templates/rules/frontend-architecture-contract-generation-rules.md +56 -0
  18. package/templates/rules/quality-architecture-contract-generation-rules.md +86 -0
  19. package/templates/rules/solutions-architecture-contract-generation-rules.md +59 -0
  20. package/templates/rules/unit-breakdown-rules.md +121 -0
  21. package/templates/skills/backend/SKILL.md +34 -0
  22. package/templates/skills/customer-experience/SKILL.md +26 -0
  23. package/templates/skills/daily-construction-update/SKILL.md +27 -0
  24. package/templates/skills/frontend/SKILL.md +30 -0
  25. package/templates/skills/github/SKILL.md +142 -0
  26. package/templates/skills/pr-review/SKILL.md +119 -0
  27. package/templates/skills/project-ticketing/SKILL.md +26 -0
  28. package/templates/skills/quality/SKILL.md +31 -0
  29. package/templates/skills/r3-analytics/SKILL.md +26 -0
  30. package/templates/skills/release-plan/SKILL.md +27 -0
  31. package/templates/skills/solutions/SKILL.md +29 -0
  32. package/templates/skills/unit-breakdown/SKILL.md +31 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Novare SQE Armory
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,138 @@
1
- # Temporary Holding Version
1
+ # nvaid
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ One CLI that installs an **AI-DLC** (AI Development Lifecycle) skills-and-workflow toolkit
4
+ into **any** coding agent, and houses a repo's intents, config, and design-code dictionary
5
+ in a single owned `nvaid/` folder.
6
+
7
+ Edit a skill once here, publish, and every consuming repo re-syncs with `nvaid update` —
8
+ no more hand-copying skills between repositories, or between one agent's config and
9
+ another's (`.claude/`, `.kiro/`, `.cursor/`, …).
10
+
11
+ ## Install & use
12
+
13
+ ```bash
14
+ npm i -g @novare-technologies/nvaid # or on demand: npx @novare-technologies/nvaid <command>
15
+
16
+ cd your-repo
17
+ nvaid init # pick coding agents, inject skills + slash commands, drop nvaid/
18
+ nvaid update # re-sync every configured agent to the installed nvaid version
19
+ nvaid validate # CI gate: fail if generated files are stale or hand-edited
20
+ ```
21
+
22
+ > Published under the `@novare-technologies` org scope as a **public** package, so anyone
23
+ > can install it. The typed command stays `nvaid` (that's the `bin`, independent of the
24
+ > package name).
25
+
26
+ `init` flags:
27
+
28
+ ```bash
29
+ nvaid init --agents claude,kiro,cursor # skip the prompt, name agents directly
30
+ nvaid init --owning-repo frontend # preset the config's owning_repo
31
+ nvaid init --yes # non-interactive (defaults to the AGENTS.md catch-all)
32
+ ```
33
+
34
+ ## How it works
35
+
36
+ - **Full skill set, no profiles.** `init` injects every skill into each selected agent. A
37
+ repo declares what it *is* in `nvaid/config.yml` (`owning_repo`), rather than receiving a
38
+ trimmed-down skill set.
39
+ - **Any coding agent.** `init` shows a multi-select. Each agent is a *path + format adapter*
40
+ (`src/agents.ts`) over one neutral source — adding a new agent is a new registry entry,
41
+ not new content. An `AGENTS.md` catch-all covers any tool that reads the cross-agent
42
+ standard but has no bespoke adapter.
43
+ - **The `nvaid/` folder is hand-owned, content only** — `config.yml`, `PROJECT-RULES.md`,
44
+ `DESIGN-CODE-DICTIONARY.md`, and `intents/`. `update` never touches these. The *generated*
45
+ skill/command files live in each agent's own directory and carry a "do not edit" header.
46
+ - **`PROJECT-RULES.md` and `DESIGN-CODE-DICTIONARY.md` are per-project templates**, not
47
+ shipped content: `init` drops fill-in skeletons for a developer to own. nvaid ships only
48
+ the universal skills and the contract/unit-decomposition rules.
49
+ - **Drift is caught, not just avoided.** `validate` fails (exit 1) if a repo's generated
50
+ files are stale (behind the installed version), missing, or hand-edited — turning silent
51
+ divergence into a failing CI check.
52
+
53
+ ## The workflow (slash commands)
54
+
55
+ `init` installs four gated commands. They run in order, with a human gate after the intent:
56
+
57
+ ```
58
+ /nvaid:intent draft the intent — business need + success criteria only, then STOP
59
+ ⏸ stakeholder approval
60
+ /nvaid:contracts draft the per-role architecture contracts
61
+ /nvaid:breakdown cut into units (each tagged with its owning repo) — the final cut
62
+ /nvaid:apply generate per-repo unit docs + open construction branches
63
+ ```
64
+
65
+ Contracts precede the breakdown deliberately (the contract skills run across the
66
+ elaboration phases; the unit-breakdown is their final output).
67
+
68
+ ## Repository layout
69
+
70
+ ```
71
+ nvaid/
72
+ package.json # bin: nvaid; ships dist/ + templates/
73
+ tsconfig.json
74
+ src/
75
+ cli.ts # command wiring (commander)
76
+ agents.ts # the agent registry — 9 bespoke + an AGENTS.md catch-all
77
+ paths.ts # resolves the bundled templates/
78
+ config.ts # read/patch a repo's nvaid/config.yml
79
+ scaffold.ts # drop the nvaid/ folder + sync nvaid/rules
80
+ generate.ts # neutral source -> per-agent files (+ drift diff)
81
+ commands/
82
+ init.ts # agent multi-select + inject + scaffold nvaid/
83
+ update.ts # re-sync configured agents
84
+ validate.ts # drift gate
85
+ templates/ # the payload shipped in the package
86
+ skills/ # 12 skills (6 ceremony + frontend + backend + 4 tool)
87
+ rules/ # 7 flow/rule files (contract-gen, unit-breakdown, tracker)
88
+ commands/ # the 4 slash-command defs
89
+ nvaid/ # the folder init drops into a repo (all hand-owned)
90
+ config.yml
91
+ PROJECT-RULES.md # fill-in template
92
+ DESIGN-CODE-DICTIONARY.md # fill-in template (a technical README for the code)
93
+ intents/
94
+ ```
95
+
96
+ ### Skills included (`templates/skills/`)
97
+
98
+ - **Ceremony:** `customer-experience`, `solutions`, `quality`, `unit-breakdown`,
99
+ `r3-analytics`, `release-plan`
100
+ - **Tier:** `frontend`, `backend`
101
+ - **Tool:** `github`, `project-ticketing`, `pr-review`, `daily-construction-update`
102
+
103
+ ## Development
104
+
105
+ ```bash
106
+ npm install
107
+ npm run typecheck
108
+ npm run dev -- init --agents claude # run the CLI from source (tsx)
109
+ npm run build # bundle src/ -> dist/cli.js (tsup)
110
+ npm test # vitest
111
+ ```
112
+
113
+ ## Status & next steps
114
+
115
+ The CLI (`init` / `update` / `validate`) is implemented and runs end-to-end. Still open:
116
+
117
+ - Tests (`vitest`) for the generator and the agent adapters.
118
+ - Confirm each agent's target path against its current docs before relying on its adapter
119
+ (agent conventions change often; `AGENTS.md` is the safe default).
120
+ ### Non-JS repos (Python, Go, etc.)
121
+
122
+ No special delivery path needed. nvaid only *writes files* — skills, the guardrail
123
+ templates, and the `nvaid/` folder — and exits; the output has no runtime dependency on
124
+ Node. A Python repo just runs the command once:
125
+
126
+ ```bash
127
+ npx @novare-technologies/nvaid init
128
+ ```
129
+
130
+ Nothing is added to the target project itself (no `devDependency`, no Node toolchain in
131
+ the repo). The only requirement is that Node is available on the developer's machine or CI
132
+ to invoke the CLI — a one-time "have Node installed to run a scaffolding tool" ask, not an
133
+ ongoing footprint. (A prebuilt binary or pipx wrapper was considered and dropped as
134
+ unnecessary.)
135
+
136
+ ---
137
+
138
+ Authored and maintained by **Novare SQE Armory**.
package/dist/cli.js ADDED
@@ -0,0 +1,483 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/commands/init.ts
7
+ import * as p from "@clack/prompts";
8
+ import pc from "picocolors";
9
+
10
+ // src/agents.ts
11
+ var AGENTS = [
12
+ {
13
+ id: "claude",
14
+ label: "Claude Code",
15
+ skillsPath: ".claude/skills",
16
+ instructionsFile: "CLAUDE.md",
17
+ format: "markdown",
18
+ bespoke: true
19
+ },
20
+ {
21
+ id: "kiro",
22
+ label: "Kiro",
23
+ skillsPath: ".kiro/skills",
24
+ instructionsFile: ".kiro/steering/nvaid.md",
25
+ format: "markdown",
26
+ bespoke: true
27
+ },
28
+ {
29
+ id: "cursor",
30
+ label: "Cursor",
31
+ skillsPath: ".cursor/rules",
32
+ format: "mdc",
33
+ bespoke: true
34
+ },
35
+ {
36
+ id: "copilot",
37
+ label: "GitHub Copilot",
38
+ skillsPath: ".github",
39
+ instructionsFile: ".github/copilot-instructions.md",
40
+ format: "markdown",
41
+ bespoke: true
42
+ },
43
+ {
44
+ id: "windsurf",
45
+ label: "Windsurf",
46
+ skillsPath: ".windsurf/rules",
47
+ format: "markdown",
48
+ bespoke: true
49
+ },
50
+ {
51
+ id: "amazonq",
52
+ label: "Amazon Q",
53
+ skillsPath: ".amazonq/rules",
54
+ format: "markdown",
55
+ bespoke: true
56
+ },
57
+ {
58
+ id: "cline",
59
+ label: "Cline",
60
+ skillsPath: ".cline/rules",
61
+ instructionsFile: ".clinerules",
62
+ format: "markdown",
63
+ bespoke: true
64
+ },
65
+ {
66
+ id: "gemini",
67
+ label: "Gemini Code Assist",
68
+ skillsPath: ".gemini",
69
+ instructionsFile: ".gemini/styleguide.md",
70
+ format: "markdown",
71
+ bespoke: true
72
+ },
73
+ {
74
+ id: "aider",
75
+ label: "Aider",
76
+ skillsPath: ".nvaid/skills",
77
+ instructionsFile: "AGENTS.md",
78
+ format: "markdown",
79
+ bespoke: true
80
+ },
81
+ {
82
+ id: "agents-md",
83
+ label: "Any AGENTS.md-compatible tool (catch-all)",
84
+ skillsPath: ".nvaid/skills",
85
+ instructionsFile: "AGENTS.md",
86
+ format: "markdown",
87
+ bespoke: false
88
+ }
89
+ ];
90
+ function getAgent(id) {
91
+ return AGENTS.find((a) => a.id === id);
92
+ }
93
+
94
+ // src/generate.ts
95
+ import { readFile, writeFile, mkdir, readdir, rm } from "fs/promises";
96
+ import { existsSync } from "fs";
97
+ import { join, relative } from "path";
98
+
99
+ // src/paths.ts
100
+ import { fileURLToPath } from "url";
101
+ import { dirname, resolve } from "path";
102
+ var here = dirname(fileURLToPath(import.meta.url));
103
+ var TEMPLATES_DIR = resolve(here, "..", "templates");
104
+ var SKILLS_SRC = resolve(TEMPLATES_DIR, "skills");
105
+ var RULES_SRC = resolve(TEMPLATES_DIR, "rules");
106
+ var COMMANDS_SRC = resolve(TEMPLATES_DIR, "commands");
107
+ var NVAID_FOLDER_SRC = resolve(TEMPLATES_DIR, "nvaid");
108
+ var NVAID_DIR_NAME = "nvaid";
109
+ var CONFIG_FILE = "config.yml";
110
+ var AIDLC_ROOT_TOKEN = "{{AIDLC_ROOT}}";
111
+ async function nvaidVersion() {
112
+ const { readFile: readFile3 } = await import("fs/promises");
113
+ const pkg = JSON.parse(await readFile3(resolve(TEMPLATES_DIR, "..", "package.json"), "utf8"));
114
+ return pkg.version;
115
+ }
116
+
117
+ // src/generate.ts
118
+ var DO_NOT_EDIT = "<!-- generated by nvaid \u2014 do not edit. Run `nvaid update` to regenerate. -->";
119
+ function aidlcRootFor(agent) {
120
+ void agent;
121
+ return "nvaid/rules";
122
+ }
123
+ function withHeader(body, format) {
124
+ if (format === "mdc") {
125
+ return `${DO_NOT_EDIT}
126
+ ${body}`;
127
+ }
128
+ return `${DO_NOT_EDIT}
129
+
130
+ ${body}`;
131
+ }
132
+ async function listSkillDirs() {
133
+ const entries = await readdir(SKILLS_SRC, { withFileTypes: true });
134
+ return entries.filter((e) => e.isDirectory()).map((e) => e.name);
135
+ }
136
+ async function listCommandFiles() {
137
+ const entries = await readdir(COMMANDS_SRC, { withFileTypes: true });
138
+ return entries.filter((e) => e.isFile() && e.name.endsWith(".md")).map((e) => e.name);
139
+ }
140
+ async function renderTemplate(srcFile, agent) {
141
+ const raw = await readFile(srcFile, "utf8");
142
+ const substituted = raw.split(AIDLC_ROOT_TOKEN).join(aidlcRootFor(agent));
143
+ return withHeader(substituted, agent.format);
144
+ }
145
+ function skillTarget(projectRoot, agent, skill) {
146
+ const ext = agent.format === "mdc" ? "mdc" : "md";
147
+ return join(projectRoot, agent.skillsPath, `${skill}.${ext}`);
148
+ }
149
+ function commandTarget(projectRoot, agent, cmdFile) {
150
+ const base = cmdFile.replace(/\.md$/, "");
151
+ const ext = agent.format === "mdc" ? "mdc" : "md";
152
+ return join(projectRoot, agent.skillsPath, `commands`, `nvaid-${base}.${ext}`);
153
+ }
154
+ async function writeFileEnsuringDir(path, body, force, result) {
155
+ if (!force && existsSync(path)) {
156
+ result.skipped.push(path);
157
+ return;
158
+ }
159
+ await mkdir(join(path, ".."), { recursive: true });
160
+ await writeFile(path, body, "utf8");
161
+ result.written.push(path);
162
+ }
163
+ async function generateAgentFiles(opts) {
164
+ const result = { written: [], skipped: [] };
165
+ const skills = await listSkillDirs();
166
+ const commands = await listCommandFiles();
167
+ for (const agent of opts.agents) {
168
+ for (const skill of skills) {
169
+ const src = join(SKILLS_SRC, skill, "SKILL.md");
170
+ if (!existsSync(src)) continue;
171
+ const body = await renderTemplate(src, agent);
172
+ await writeFileEnsuringDir(skillTarget(opts.projectRoot, agent, skill), body, opts.force, result);
173
+ }
174
+ for (const cmd of commands) {
175
+ const body = await renderTemplate(join(COMMANDS_SRC, cmd), agent);
176
+ await writeFileEnsuringDir(commandTarget(opts.projectRoot, agent, cmd), body, opts.force, result);
177
+ }
178
+ }
179
+ return result;
180
+ }
181
+ async function diffAgentFiles(opts) {
182
+ const drift = [];
183
+ const skills = await listSkillDirs();
184
+ const commands = await listCommandFiles();
185
+ const checks = [];
186
+ for (const agent of opts.agents) {
187
+ for (const skill of skills) {
188
+ const src = join(SKILLS_SRC, skill, "SKILL.md");
189
+ if (existsSync(src)) checks.push({ target: skillTarget(opts.projectRoot, agent, skill), src });
190
+ }
191
+ for (const cmd of commands) {
192
+ checks.push({ target: commandTarget(opts.projectRoot, agent, cmd), src: join(COMMANDS_SRC, cmd) });
193
+ }
194
+ }
195
+ for (const { target, src } of checks) {
196
+ const agent = opts.agents.find((a) => target.includes(a.skillsPath.replace(/^\.\//, "")));
197
+ const expected = await renderTemplate(src, agent ?? opts.agents[0]);
198
+ if (!existsSync(target)) {
199
+ drift.push(`${relative(opts.projectRoot, target)} (missing)`);
200
+ continue;
201
+ }
202
+ const actual = await readFile(target, "utf8");
203
+ if (actual !== expected) drift.push(relative(opts.projectRoot, target));
204
+ }
205
+ return drift;
206
+ }
207
+ async function cleanAgentDirs(projectRoot, agents) {
208
+ for (const agent of agents) {
209
+ const dir = join(projectRoot, agent.skillsPath);
210
+ const commandsDir = join(dir, "commands");
211
+ if (existsSync(commandsDir)) await rm(commandsDir, { recursive: true, force: true });
212
+ }
213
+ }
214
+
215
+ // src/scaffold.ts
216
+ import { cp, mkdir as mkdir2, readdir as readdir2 } from "fs/promises";
217
+ import { existsSync as existsSync2 } from "fs";
218
+ import { join as join2 } from "path";
219
+ async function scaffoldNvaidFolder(projectRoot) {
220
+ const dest = join2(projectRoot, NVAID_DIR_NAME);
221
+ await mkdir2(dest, { recursive: true });
222
+ const written = [];
223
+ await copyTreeSkipExisting(NVAID_FOLDER_SRC, dest, written);
224
+ return written;
225
+ }
226
+ async function syncRules(projectRoot) {
227
+ const dest = join2(projectRoot, NVAID_DIR_NAME, "rules");
228
+ await mkdir2(dest, { recursive: true });
229
+ await cp(RULES_SRC, dest, { recursive: true, force: true });
230
+ }
231
+ async function copyTreeSkipExisting(src, dest, written) {
232
+ const entries = await readdir2(src, { withFileTypes: true });
233
+ for (const entry of entries) {
234
+ const s = join2(src, entry.name);
235
+ const d = join2(dest, entry.name);
236
+ if (entry.isDirectory()) {
237
+ await mkdir2(d, { recursive: true });
238
+ await copyTreeSkipExisting(s, d, written);
239
+ } else {
240
+ if (existsSync2(d)) continue;
241
+ await cp(s, d);
242
+ written.push(d);
243
+ }
244
+ }
245
+ }
246
+
247
+ // src/config.ts
248
+ import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
249
+ import { existsSync as existsSync3 } from "fs";
250
+ import { join as join3 } from "path";
251
+ import { parse } from "yaml";
252
+ var EMPTY_STACK = {
253
+ languages: [],
254
+ runtime: "",
255
+ frameworks: [],
256
+ package_manager: "",
257
+ build: "",
258
+ test: "",
259
+ lint_format: "",
260
+ database: "",
261
+ deploy: "",
262
+ additional: {}
263
+ };
264
+ function configPath(projectRoot) {
265
+ return join3(projectRoot, NVAID_DIR_NAME, CONFIG_FILE);
266
+ }
267
+ function hasConfig(projectRoot) {
268
+ return existsSync3(configPath(projectRoot));
269
+ }
270
+ async function readConfig(projectRoot) {
271
+ const raw = await readFile2(configPath(projectRoot), "utf8");
272
+ const parsed = parse(raw) ?? {};
273
+ const install = parsed.install ?? {};
274
+ return {
275
+ schema: parsed.schema ?? "nvaid/v1",
276
+ install: {
277
+ config: install.config === true,
278
+ project_rules: install.project_rules === true,
279
+ design_code_dictionary: install.design_code_dictionary === true
280
+ },
281
+ project_name: parsed.project_name ?? "",
282
+ owning_repo: parsed.owning_repo ?? "",
283
+ tech_stack: { ...EMPTY_STACK, ...parsed.tech_stack ?? {} },
284
+ agents: Array.isArray(parsed.agents) ? parsed.agents : [],
285
+ context: parsed.context ?? "",
286
+ generated_with: parsed.generated_with ?? ""
287
+ };
288
+ }
289
+ async function setConfigFields(projectRoot, fields) {
290
+ const path = configPath(projectRoot);
291
+ let text = await readFile2(path, "utf8");
292
+ if (fields.project_name !== void 0) {
293
+ text = replaceScalar(text, "project_name", `"${fields.project_name}"`);
294
+ }
295
+ if (fields.owning_repo !== void 0) {
296
+ text = replaceScalar(text, "owning_repo", `"${fields.owning_repo}"`);
297
+ }
298
+ if (fields.generated_with !== void 0) {
299
+ text = replaceScalar(text, "generated_with", `"${fields.generated_with}"`);
300
+ }
301
+ if (fields.agents !== void 0) {
302
+ text = replaceAgents(text, fields.agents);
303
+ }
304
+ await writeFile2(path, text, "utf8");
305
+ }
306
+ function replaceScalar(text, key, value) {
307
+ const re = new RegExp(`^(${key}:).*$`, "m");
308
+ return re.test(text) ? text.replace(re, `$1 ${value}`) : `${text}
309
+ ${key}: ${value}
310
+ `;
311
+ }
312
+ function replaceAgents(text, agents) {
313
+ const flow = agents.length ? `[${agents.map((a) => `"${a}"`).join(", ")}]` : "[]";
314
+ const re = /^agents:.*(?:\n[ \t]*-.*)*$/m;
315
+ const line = `agents: ${flow}`;
316
+ return re.test(text) ? text.replace(re, line) : `${text}
317
+ ${line}
318
+ `;
319
+ }
320
+
321
+ // src/commands/init.ts
322
+ async function runInit(opts) {
323
+ const projectRoot = process.cwd();
324
+ if (hasConfig(projectRoot) && !opts.yes) {
325
+ const proceed = await p.confirm({
326
+ message: "This repo already has nvaid/config.yml. Re-run init anyway (update is usually what you want)?",
327
+ initialValue: false
328
+ });
329
+ if (p.isCancel(proceed) || !proceed) {
330
+ p.cancel("Nothing changed. Run `nvaid update` to re-sync agents.");
331
+ return;
332
+ }
333
+ }
334
+ let selected;
335
+ if (opts.agents?.length) {
336
+ selected = opts.agents.map(resolveAgentOrThrow);
337
+ } else if (opts.yes) {
338
+ selected = [getAgent("agents-md")];
339
+ } else {
340
+ p.intro(pc.bold("nvaid init"));
341
+ const choice = await p.multiselect({
342
+ message: "Select the coding agents to set up (space to toggle, enter to confirm):",
343
+ options: AGENTS.map((a) => ({ value: a.id, label: a.label, hint: a.skillsPath })),
344
+ required: true
345
+ });
346
+ if (p.isCancel(choice)) {
347
+ p.cancel("Cancelled.");
348
+ return;
349
+ }
350
+ selected = choice.map(resolveAgentOrThrow);
351
+ }
352
+ const gen = await generateAgentFiles({ projectRoot, agents: selected, force: false });
353
+ const scaffolded = await scaffoldNvaidFolder(projectRoot);
354
+ await syncRules(projectRoot);
355
+ await setConfigFields(projectRoot, {
356
+ agents: selected.map((a) => a.id),
357
+ owning_repo: opts.owningRepo ?? "<set-me>",
358
+ ...opts.projectName ? { project_name: opts.projectName } : {},
359
+ generated_with: await nvaidVersion()
360
+ });
361
+ p.note(
362
+ [
363
+ `agents: ${selected.map((a) => a.label).join(", ")}`,
364
+ `written: ${gen.written.length} agent files` + (gen.skipped.length ? `, ${gen.skipped.length} skipped (existing)` : ""),
365
+ `nvaid/: ${scaffolded.length ? scaffolded.length + " files created" : "already present"}`
366
+ ].join("\n"),
367
+ "Done"
368
+ );
369
+ p.outro(
370
+ [
371
+ "Next, in your AI chat \u2014 run this FIRST:",
372
+ " /nvaid:install scan the project, fill config.yml + PROJECT-RULES + dictionary",
373
+ "",
374
+ "The workflow commands are blocked until /nvaid:install completes:",
375
+ ' /nvaid:intent "..." draft the intent, then STOP for stakeholder approval',
376
+ " /nvaid:contracts after approval \u2014 draft the per-role contracts",
377
+ " /nvaid:breakdown cut into units (tagged by owning repo)",
378
+ " /nvaid:apply generate per-repo unit docs + branches"
379
+ ].join("\n")
380
+ );
381
+ }
382
+ function resolveAgentOrThrow(id) {
383
+ const a = getAgent(id);
384
+ if (!a) throw new Error(`Unknown agent id: "${id}". Known: ${AGENTS.map((x) => x.id).join(", ")}`);
385
+ return a;
386
+ }
387
+
388
+ // src/commands/update.ts
389
+ import * as p2 from "@clack/prompts";
390
+ import pc2 from "picocolors";
391
+ async function runUpdate() {
392
+ const projectRoot = process.cwd();
393
+ if (!hasConfig(projectRoot)) {
394
+ throw new Error("No nvaid/config.yml here. Run `nvaid init` first.");
395
+ }
396
+ const cfg = await readConfig(projectRoot);
397
+ if (!cfg.agents.length) {
398
+ throw new Error("config.yml lists no agents. Run `nvaid init` to select some.");
399
+ }
400
+ const agents = cfg.agents.map((id) => {
401
+ const a = getAgent(id);
402
+ if (!a) throw new Error(`config.yml references unknown agent "${id}".`);
403
+ return a;
404
+ });
405
+ const version = await nvaidVersion();
406
+ p2.intro(pc2.bold(`nvaid update \u2192 ${version}`));
407
+ await cleanAgentDirs(projectRoot, agents);
408
+ const gen = await generateAgentFiles({ projectRoot, agents, force: true });
409
+ await syncRules(projectRoot);
410
+ await setConfigFields(projectRoot, { generated_with: version });
411
+ p2.note(
412
+ [
413
+ `agents: ${agents.map((a) => a.label).join(", ")}`,
414
+ `rewrote: ${gen.written.length} agent files`,
415
+ `rules: refreshed nvaid/rules/`,
416
+ `was: ${cfg.generated_with || "unknown"} \u2192 now: ${version}`
417
+ ].join("\n"),
418
+ "Updated"
419
+ );
420
+ p2.outro("Hand-owned files (config.yml, PROJECT-RULES.md, DESIGN-CODE-DICTIONARY.md, intents/) untouched.");
421
+ }
422
+
423
+ // src/commands/validate.ts
424
+ import pc3 from "picocolors";
425
+ async function runValidate() {
426
+ const projectRoot = process.cwd();
427
+ if (!hasConfig(projectRoot)) {
428
+ console.error(pc3.red("\u2717 No nvaid/config.yml here. Run `nvaid init` first."));
429
+ return 1;
430
+ }
431
+ const cfg = await readConfig(projectRoot);
432
+ const agents = [];
433
+ for (const id of cfg.agents) {
434
+ const a = getAgent(id);
435
+ if (!a) {
436
+ console.error(pc3.red(`\u2717 config.yml references unknown agent "${id}".`));
437
+ return 1;
438
+ }
439
+ agents.push(a);
440
+ }
441
+ const version = await nvaidVersion();
442
+ let failed = false;
443
+ if (cfg.generated_with && cfg.generated_with !== version) {
444
+ console.error(
445
+ pc3.yellow(
446
+ `\u26A0 generated with nvaid ${cfg.generated_with}, installed is ${version}. Run \`nvaid update\`.`
447
+ )
448
+ );
449
+ failed = true;
450
+ }
451
+ const drift = await diffAgentFiles({ projectRoot, agents, force: true });
452
+ if (drift.length) {
453
+ console.error(pc3.red(`\u2717 ${drift.length} generated file(s) stale or hand-edited:`));
454
+ for (const f of drift) console.error(pc3.red(` ${f}`));
455
+ console.error(pc3.dim(" Run `nvaid update` to regenerate (hand edits belong in the nvaid source, not here)."));
456
+ failed = true;
457
+ }
458
+ if (failed) return 1;
459
+ console.log(pc3.green(`\u2713 nvaid ${version}: ${agents.length} agent(s) in sync, no drift.`));
460
+ return 0;
461
+ }
462
+
463
+ // src/cli.ts
464
+ var program = new Command();
465
+ program.name("nvaid").description("nvaid \u2014 install an AI-DLC skills + workflow toolkit into any coding agent").version(await nvaidVersion());
466
+ program.command("init").description("Onboard this repo: select coding agents, inject skills, create the nvaid/ folder").option("--agents <ids>", "comma-separated agent ids (skips the prompt)", (v) => v.split(",")).option("--owning-repo <tier>", "owning_repo value written to config.yml").option("--project-name <name>", "project_name value written to config.yml").option("-y, --yes", "accept defaults, no prompts (CI)").action(async (opts) => {
467
+ await runInit({
468
+ agents: opts.agents,
469
+ owningRepo: opts.owningRepo,
470
+ projectName: opts.projectName,
471
+ yes: opts.yes
472
+ });
473
+ });
474
+ program.command("update").description("Re-sync agent files to the installed nvaid version").action(async () => {
475
+ await runUpdate();
476
+ });
477
+ program.command("validate").description("Fail if generated agent files are stale or hand-edited").action(async () => {
478
+ process.exit(await runValidate());
479
+ });
480
+ program.parseAsync().catch((err) => {
481
+ console.error(err instanceof Error ? err.message : err);
482
+ process.exit(1);
483
+ });
package/package.json CHANGED
@@ -1,6 +1,51 @@
1
1
  {
2
2
  "name": "@novare-technologies/nvaid",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "nvaid — one CLI that installs an AI-DLC skills-and-workflow toolkit into any coding agent, and houses intents, config, and the design-code dictionary in a single nvaid/ folder.",
5
+ "type": "module",
6
+ "bin": {
7
+ "nvaid": "dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "templates"
12
+ ],
13
+ "engines": {
14
+ "node": ">=18"
15
+ },
16
+ "scripts": {
17
+ "build": "tsup src/cli.ts --format esm --clean",
18
+ "dev": "tsx src/cli.ts",
19
+ "test": "vitest run --passWithNoTests",
20
+ "typecheck": "tsc --noEmit",
21
+ "prepublishOnly": "npm run build"
22
+ },
23
+ "keywords": [
24
+ "ai-dlc",
25
+ "coding-agent",
26
+ "skills",
27
+ "scaffold",
28
+ "cli"
29
+ ],
30
+ "author": "Novare SQE Armory",
31
+ "license": "MIT",
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "dependencies": {
36
+ "@clack/prompts": "^0.7.0",
37
+ "commander": "^12.0.0",
38
+ "picocolors": "^1.0.0",
39
+ "yaml": "^2.4.0"
40
+ },
41
+ "devDependencies": {
42
+ "@types/node": "^22.0.0",
43
+ "tsup": "^8.0.0",
44
+ "tsx": "^4.7.0",
45
+ "typescript": "^5.4.0",
46
+ "vitest": "^5.0.3"
47
+ },
48
+ "overrides": {
49
+ "esbuild": "^0.28.2"
50
+ }
51
+ }