@novare-technologies/nvaid 0.0.0-stage → 0.1.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 (30) hide show
  1. package/README.md +137 -2
  2. package/dist/cli.js +476 -0
  3. package/package.json +46 -4
  4. package/templates/commands/apply.md +28 -0
  5. package/templates/commands/breakdown.md +27 -0
  6. package/templates/commands/contracts.md +35 -0
  7. package/templates/commands/intent.md +32 -0
  8. package/templates/nvaid/DESIGN-CODE-DICTIONARY.md +90 -0
  9. package/templates/nvaid/PROJECT-RULES.md +93 -0
  10. package/templates/nvaid/config.yml +74 -0
  11. package/templates/nvaid/intents/.gitkeep +0 -0
  12. package/templates/rules/ISSUE-TRACKER-TEMPLATE.md +221 -0
  13. package/templates/rules/backend-architecture-contract-generation-rules.md +56 -0
  14. package/templates/rules/customer-experience-contract-generation-rules.md +51 -0
  15. package/templates/rules/frontend-architecture-contract-generation-rules.md +56 -0
  16. package/templates/rules/quality-architecture-contract-generation-rules.md +86 -0
  17. package/templates/rules/solutions-architecture-contract-generation-rules.md +59 -0
  18. package/templates/rules/unit-breakdown-rules.md +121 -0
  19. package/templates/skills/backend/SKILL.md +34 -0
  20. package/templates/skills/customer-experience/SKILL.md +26 -0
  21. package/templates/skills/daily-construction-update/SKILL.md +27 -0
  22. package/templates/skills/frontend/SKILL.md +30 -0
  23. package/templates/skills/github/SKILL.md +142 -0
  24. package/templates/skills/pr-review/SKILL.md +119 -0
  25. package/templates/skills/project-ticketing/SKILL.md +26 -0
  26. package/templates/skills/quality/SKILL.md +31 -0
  27. package/templates/skills/r3-analytics/SKILL.md +26 -0
  28. package/templates/skills/release-plan/SKILL.md +27 -0
  29. package/templates/skills/solutions/SKILL.md +29 -0
  30. package/templates/skills/unit-breakdown/SKILL.md +31 -0
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,476 @@
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
+ return {
274
+ schema: parsed.schema ?? "nvaid/v1",
275
+ project_name: parsed.project_name ?? "",
276
+ owning_repo: parsed.owning_repo ?? "",
277
+ tech_stack: { ...EMPTY_STACK, ...parsed.tech_stack ?? {} },
278
+ agents: Array.isArray(parsed.agents) ? parsed.agents : [],
279
+ context: parsed.context ?? "",
280
+ generated_with: parsed.generated_with ?? ""
281
+ };
282
+ }
283
+ async function setConfigFields(projectRoot, fields) {
284
+ const path = configPath(projectRoot);
285
+ let text = await readFile2(path, "utf8");
286
+ if (fields.project_name !== void 0) {
287
+ text = replaceScalar(text, "project_name", `"${fields.project_name}"`);
288
+ }
289
+ if (fields.owning_repo !== void 0) {
290
+ text = replaceScalar(text, "owning_repo", `"${fields.owning_repo}"`);
291
+ }
292
+ if (fields.generated_with !== void 0) {
293
+ text = replaceScalar(text, "generated_with", `"${fields.generated_with}"`);
294
+ }
295
+ if (fields.agents !== void 0) {
296
+ text = replaceAgents(text, fields.agents);
297
+ }
298
+ await writeFile2(path, text, "utf8");
299
+ }
300
+ function replaceScalar(text, key, value) {
301
+ const re = new RegExp(`^(${key}:).*$`, "m");
302
+ return re.test(text) ? text.replace(re, `$1 ${value}`) : `${text}
303
+ ${key}: ${value}
304
+ `;
305
+ }
306
+ function replaceAgents(text, agents) {
307
+ const flow = agents.length ? `[${agents.map((a) => `"${a}"`).join(", ")}]` : "[]";
308
+ const re = /^agents:.*(?:\n[ \t]*-.*)*$/m;
309
+ const line = `agents: ${flow}`;
310
+ return re.test(text) ? text.replace(re, line) : `${text}
311
+ ${line}
312
+ `;
313
+ }
314
+
315
+ // src/commands/init.ts
316
+ async function runInit(opts) {
317
+ const projectRoot = process.cwd();
318
+ if (hasConfig(projectRoot) && !opts.yes) {
319
+ const proceed = await p.confirm({
320
+ message: "This repo already has nvaid/config.yml. Re-run init anyway (update is usually what you want)?",
321
+ initialValue: false
322
+ });
323
+ if (p.isCancel(proceed) || !proceed) {
324
+ p.cancel("Nothing changed. Run `nvaid update` to re-sync agents.");
325
+ return;
326
+ }
327
+ }
328
+ let selected;
329
+ if (opts.agents?.length) {
330
+ selected = opts.agents.map(resolveAgentOrThrow);
331
+ } else if (opts.yes) {
332
+ selected = [getAgent("agents-md")];
333
+ } else {
334
+ p.intro(pc.bold("nvaid init"));
335
+ const choice = await p.multiselect({
336
+ message: "Select the coding agents to set up (space to toggle, enter to confirm):",
337
+ options: AGENTS.map((a) => ({ value: a.id, label: a.label, hint: a.skillsPath })),
338
+ required: true
339
+ });
340
+ if (p.isCancel(choice)) {
341
+ p.cancel("Cancelled.");
342
+ return;
343
+ }
344
+ selected = choice.map(resolveAgentOrThrow);
345
+ }
346
+ const gen = await generateAgentFiles({ projectRoot, agents: selected, force: false });
347
+ const scaffolded = await scaffoldNvaidFolder(projectRoot);
348
+ await syncRules(projectRoot);
349
+ await setConfigFields(projectRoot, {
350
+ agents: selected.map((a) => a.id),
351
+ owning_repo: opts.owningRepo ?? "<set-me>",
352
+ ...opts.projectName ? { project_name: opts.projectName } : {},
353
+ generated_with: await nvaidVersion()
354
+ });
355
+ p.note(
356
+ [
357
+ `agents: ${selected.map((a) => a.label).join(", ")}`,
358
+ `written: ${gen.written.length} agent files` + (gen.skipped.length ? `, ${gen.skipped.length} skipped (existing)` : ""),
359
+ `nvaid/: ${scaffolded.length ? scaffolded.length + " files created" : "already present"}`
360
+ ].join("\n"),
361
+ "Done"
362
+ );
363
+ p.outro(
364
+ [
365
+ "Next, in your AI chat:",
366
+ ' /nvaid:intent "..." draft the intent, then STOP for stakeholder approval',
367
+ " /nvaid:contracts after approval \u2014 draft the per-role contracts",
368
+ " /nvaid:breakdown cut into units (tagged by owning repo)",
369
+ " /nvaid:apply generate per-repo unit docs + branches",
370
+ "",
371
+ "Fill in nvaid/PROJECT-RULES.md and set owning_repo in nvaid/config.yml."
372
+ ].join("\n")
373
+ );
374
+ }
375
+ function resolveAgentOrThrow(id) {
376
+ const a = getAgent(id);
377
+ if (!a) throw new Error(`Unknown agent id: "${id}". Known: ${AGENTS.map((x) => x.id).join(", ")}`);
378
+ return a;
379
+ }
380
+
381
+ // src/commands/update.ts
382
+ import * as p2 from "@clack/prompts";
383
+ import pc2 from "picocolors";
384
+ async function runUpdate() {
385
+ const projectRoot = process.cwd();
386
+ if (!hasConfig(projectRoot)) {
387
+ throw new Error("No nvaid/config.yml here. Run `nvaid init` first.");
388
+ }
389
+ const cfg = await readConfig(projectRoot);
390
+ if (!cfg.agents.length) {
391
+ throw new Error("config.yml lists no agents. Run `nvaid init` to select some.");
392
+ }
393
+ const agents = cfg.agents.map((id) => {
394
+ const a = getAgent(id);
395
+ if (!a) throw new Error(`config.yml references unknown agent "${id}".`);
396
+ return a;
397
+ });
398
+ const version = await nvaidVersion();
399
+ p2.intro(pc2.bold(`nvaid update \u2192 ${version}`));
400
+ await cleanAgentDirs(projectRoot, agents);
401
+ const gen = await generateAgentFiles({ projectRoot, agents, force: true });
402
+ await syncRules(projectRoot);
403
+ await setConfigFields(projectRoot, { generated_with: version });
404
+ p2.note(
405
+ [
406
+ `agents: ${agents.map((a) => a.label).join(", ")}`,
407
+ `rewrote: ${gen.written.length} agent files`,
408
+ `rules: refreshed nvaid/rules/`,
409
+ `was: ${cfg.generated_with || "unknown"} \u2192 now: ${version}`
410
+ ].join("\n"),
411
+ "Updated"
412
+ );
413
+ p2.outro("Hand-owned files (config.yml, PROJECT-RULES.md, DESIGN-CODE-DICTIONARY.md, intents/) untouched.");
414
+ }
415
+
416
+ // src/commands/validate.ts
417
+ import pc3 from "picocolors";
418
+ async function runValidate() {
419
+ const projectRoot = process.cwd();
420
+ if (!hasConfig(projectRoot)) {
421
+ console.error(pc3.red("\u2717 No nvaid/config.yml here. Run `nvaid init` first."));
422
+ return 1;
423
+ }
424
+ const cfg = await readConfig(projectRoot);
425
+ const agents = [];
426
+ for (const id of cfg.agents) {
427
+ const a = getAgent(id);
428
+ if (!a) {
429
+ console.error(pc3.red(`\u2717 config.yml references unknown agent "${id}".`));
430
+ return 1;
431
+ }
432
+ agents.push(a);
433
+ }
434
+ const version = await nvaidVersion();
435
+ let failed = false;
436
+ if (cfg.generated_with && cfg.generated_with !== version) {
437
+ console.error(
438
+ pc3.yellow(
439
+ `\u26A0 generated with nvaid ${cfg.generated_with}, installed is ${version}. Run \`nvaid update\`.`
440
+ )
441
+ );
442
+ failed = true;
443
+ }
444
+ const drift = await diffAgentFiles({ projectRoot, agents, force: true });
445
+ if (drift.length) {
446
+ console.error(pc3.red(`\u2717 ${drift.length} generated file(s) stale or hand-edited:`));
447
+ for (const f of drift) console.error(pc3.red(` ${f}`));
448
+ console.error(pc3.dim(" Run `nvaid update` to regenerate (hand edits belong in the nvaid source, not here)."));
449
+ failed = true;
450
+ }
451
+ if (failed) return 1;
452
+ console.log(pc3.green(`\u2713 nvaid ${version}: ${agents.length} agent(s) in sync, no drift.`));
453
+ return 0;
454
+ }
455
+
456
+ // src/cli.ts
457
+ var program = new Command();
458
+ program.name("nvaid").description("nvaid \u2014 install an AI-DLC skills + workflow toolkit into any coding agent").version("0.1.0");
459
+ 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) => {
460
+ await runInit({
461
+ agents: opts.agents,
462
+ owningRepo: opts.owningRepo,
463
+ projectName: opts.projectName,
464
+ yes: opts.yes
465
+ });
466
+ });
467
+ program.command("update").description("Re-sync agent files to the installed nvaid version").action(async () => {
468
+ await runUpdate();
469
+ });
470
+ program.command("validate").description("Fail if generated agent files are stale or hand-edited").action(async () => {
471
+ process.exit(await runValidate());
472
+ });
473
+ program.parseAsync().catch((err) => {
474
+ console.error(err instanceof Error ? err.message : err);
475
+ process.exit(1);
476
+ });
package/package.json CHANGED
@@ -1,6 +1,48 @@
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.1.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": "^20.0.0",
43
+ "tsup": "^8.0.0",
44
+ "tsx": "^4.7.0",
45
+ "typescript": "^5.4.0",
46
+ "vitest": "^1.4.0"
47
+ }
48
+ }
@@ -0,0 +1,28 @@
1
+ ---
2
+ command: nvaid:apply
3
+ summary: Generate per-repo unit design docs and open construction branches for an approved breakdown.
4
+ stage: "Fig. 2 Stage 4+ — Per-Role Authoring / Construction"
5
+ precondition: unit-breakdown approved (Decomposition-Approved gate green)
6
+ ---
7
+
8
+ # /nvaid:apply
9
+
10
+ Turn an **approved** unit-breakdown into construction-ready work: for each Unit, generate
11
+ its design doc in the Owning Repo named by the breakdown, and open the construction branch
12
+ per the `github` Skill.
13
+
14
+ ## Precondition
15
+
16
+ Only run once the unit-breakdown is approved. If it's still awaiting the gate, stop.
17
+
18
+ ## Steps
19
+
20
+ 1. Read the approved `unit-breakdown.md` and its Units + Owning Repo tags.
21
+ 2. For each Unit, in its Owning Repo:
22
+ - generate `nvaid/intents/<INTENT-ID>-<slug>/units/<UNIT-ID>-design.md` with the Unit's
23
+ own Acceptance Criteria, referencing the relevant contracts (`covers_units`).
24
+ - cut the construction branch (`feature/<story-id>-<slug>` or `bugfix/...`) per the
25
+ `github` Skill.
26
+ 3. Create/update the GitHub Project items per `ISSUE-TRACKER-TEMPLATE.md` (Epic per Intent,
27
+ Story/Bug per Unit) — links, not pasted content.
28
+ 4. Hand off to Construction. Do not implement code here.