nikos 0.0.2 → 0.0.4

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/README.md CHANGED
@@ -1,12 +1,17 @@
1
- ```text
2
- _ _ ___ ____
3
- _ __ (_) | | __ / _ \ / ___|
4
- | '_ \ | | | |/ / | | | | \___ \
5
- | | | | | | | < | |_| | ___) |
6
- |_| |_| |_| |_|\_\ \___/ |____/
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/nikovijay/NikOS/main/assets/header.svg" alt="nikOS" width="840">
3
+ </p>
7
4
 
8
- spec-driven development for AI coding agents
9
- ```
5
+ <p align="center"><b>Spec-driven development for AI coding agents</b></p>
6
+
7
+ I'm [@nikovijay](https://x.com/nikovijay), a Product Director with 20 years
8
+ of experience in software delivery. nikOS is my personal framework for
9
+ building products with AI coding agents. I'm sharing it to help others get
10
+ started with agentic programming practices. For where I think this is going,
11
+ see my thoughts on
12
+ [the software factory of the near future](https://www.nikovijay.com/software-factory).
13
+
14
+ ## What it does
10
15
 
11
16
  AI coding agents are quick to say "done". nikOS makes them prove it. Your
12
17
  story map, plans and acceptance criteria live in your repo as plain files,
@@ -15,6 +20,10 @@ be marked done until its scenarios pass. Your agent follows a named playbook
15
20
  for each request instead of improvising, and every decision is written down
16
21
  where the next person (or agent) can find it.
17
22
 
23
+ It works with Claude Code, Codex and Cursor, and brings spec-driven
24
+ development, behaviour-driven development (BDD) with Gherkin and Cucumber,
25
+ user story mapping and acceptance criteria to agentic coding.
26
+
18
27
  > Early work in progress. Open source, MIT licensed.
19
28
 
20
29
  ## Try it in 10 minutes
@@ -72,6 +81,7 @@ Every file is plain text you can read, review and diff. The full format:
72
81
  | --- | --- |
73
82
  | `nikos init` | Create `intent/` in this repo |
74
83
  | `nikos validate` | Check `intent/` is well formed |
84
+ | `nikos story add "<title>"` | Add a story to the map, state and a starter feature |
75
85
  | `nikos clear <id>` | Check a story is ready to build |
76
86
  | `nikos plan new <id>` | Start the next version of a story's plan |
77
87
  | `nikos verify setup` | Add a Cucumber runner for this repo's stack |
@@ -83,20 +93,6 @@ Every file is plain text you can read, review and diff. The full format:
83
93
  | `nikos decisions "<q>"` | Quote the decisions about a topic or story |
84
94
  | `nikos bet new <id>` | Write a time-boxed bet card linked to its stories |
85
95
 
86
- ## Working with a team: intentdocs
87
-
88
- When you want a visual story map, reviews and a team around the same specs,
89
- [intentdocs](https://www.intentdocs.com) reads the same `intent/` folder:
90
-
91
- ```bash
92
- npx intentdocs push # upload intent/ and link this repo
93
- npx intentdocs pull # bring web edits back, shown as diffs first
94
- ```
95
-
96
- The repo stays the source of truth for specs and plans; intentdocs runs the
97
- build and acceptance steps. What maps to what:
98
- [docs/intentdocs-mapping.md](docs/intentdocs-mapping.md).
99
-
100
96
  ## Links
101
97
 
102
98
  - Home: https://nikos.tools
package/dist/cli.js CHANGED
@@ -10,6 +10,7 @@ import { runDecide } from "./commands/decide.js";
10
10
  import { runBet } from "./commands/bet.js";
11
11
  import { runShow } from "./commands/show.js";
12
12
  import { runDecisions } from "./commands/decisions.js";
13
+ import { runStory } from "./commands/story.js";
13
14
  const commands = {
14
15
  init: runInit,
15
16
  validate: runValidate,
@@ -22,6 +23,7 @@ const commands = {
22
23
  bet: runBet,
23
24
  show: runShow,
24
25
  decisions: runDecisions,
26
+ story: runStory,
25
27
  };
26
28
  const help = `nikos — spec-driven development for AI coding agents
27
29
 
@@ -30,6 +32,8 @@ Usage: nikos <command> [options]
30
32
  Commands:
31
33
  init Create intent/ in this repo (--name, --key)
32
34
  validate [dir] Check the intent/ folder is well formed
35
+ story add "<title>" Add a story to the map, state and a starter feature
36
+ (--activity, --release, --priority)
33
37
  plan new <id> Start the next version of a story's plan
34
38
  clear <id> Check a story is Lean and Executable before building
35
39
  verify setup Set up a Cucumber runner for this repo's stack
@@ -0,0 +1,87 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { parseDocument, YAMLSeq } from "yaml";
4
+ import { findIntentDir, read } from "../lib/project.js";
5
+ import { slugify } from "./init.js";
6
+ import { validate } from "./validate.js";
7
+ const PRIORITIES = ["must", "should", "could"];
8
+ const yamlOpts = { lineWidth: 0, flowCollectionPadding: false };
9
+ /** Add a story to storymap.yaml and state.yaml, and write its starter feature file. */
10
+ export function newStory(input, dir) {
11
+ const title = input.title?.trim();
12
+ if (!title)
13
+ throw new Error("A story needs a title");
14
+ const priority = input.priority ?? "must";
15
+ if (!PRIORITIES.includes(priority))
16
+ throw new Error(`'${priority}' is not a priority (must, should, could)`);
17
+ const intentDir = findIntentDir(dir);
18
+ const mapPath = join(intentDir, "storymap.yaml");
19
+ const statePath = join(intentDir, "state.yaml");
20
+ // Documents keep comments and the order of everything we don't touch.
21
+ const map = parseDocument(read(mapPath));
22
+ const state = parseDocument(read(statePath));
23
+ const plain = map.toJS();
24
+ const activities = (plain.activities ?? []).map((a) => a?.id);
25
+ if (!activities.includes(input.activity)) {
26
+ throw new Error(`'${input.activity}' is not an activity in storymap.yaml. Activities: ${activities.join(", ") || "none"}`);
27
+ }
28
+ const releases = plain.releases ?? [];
29
+ if (!releases.length)
30
+ throw new Error("storymap.yaml has no releases to add the story to");
31
+ const index = input.release === undefined ? releases.length - 1 : releases.findIndex((r) => r?.number === input.release);
32
+ if (index < 0)
33
+ throw new Error(`There is no release ${input.release}. Releases: ${releases.map((r) => r?.number).join(", ")}`);
34
+ const release = releases[index].number;
35
+ const key = plain.key;
36
+ const taken = releases.flatMap((r) => r?.stories ?? []).map((s) => Number(s.slice(key.length + 1)) || 0);
37
+ const id = `${key}-${Math.max(0, ...taken) + 1}`;
38
+ const feature = join(intentDir, "features", input.activity, `${slugify(title)}.feature`);
39
+ if (existsSync(feature))
40
+ throw new Error(`${feature} already exists; give the story a different title`);
41
+ const stories = map.getIn(["releases", index, "stories"], true);
42
+ if (stories instanceof YAMLSeq)
43
+ stories.add(id);
44
+ else
45
+ map.setIn(["releases", index, "stories"], map.createNode([id], { flow: true }));
46
+ state.setIn(["stories", id], state.createNode({ status: "todo" }));
47
+ const text = `@${id} @release-${release} @${priority} @activity-${input.activity}
48
+ Feature: ${title}
49
+
50
+ As a <who it's for>
51
+ I want <what they can do>
52
+ So that <why it matters to them>
53
+
54
+ @AC1 @${priority}
55
+ Scenario: AC1 — <the first acceptance criterion>
56
+ Given <the starting situation>
57
+ When <the one action>
58
+ Then <what they see>
59
+ `;
60
+ mkdirSync(dirname(feature), { recursive: true });
61
+ writeFileSync(feature, text, { flag: "wx" });
62
+ writeFileSync(mapPath, map.toString(yamlOpts));
63
+ writeFileSync(statePath, state.toString(yamlOpts));
64
+ return { id, release, feature, validation: validate(intentDir) };
65
+ }
66
+ export function runStory(args) {
67
+ const [sub, title, ...rest] = args;
68
+ const opt = (n) => {
69
+ const i = rest.indexOf(`--${n}`);
70
+ return i >= 0 ? rest[i + 1] : undefined;
71
+ };
72
+ if (sub !== "add" || !title || title.startsWith("--") || !opt("activity")) {
73
+ console.error('Usage: nikos story add "<title>" --activity <id> [--release <n>] [--priority must|should|could]');
74
+ return 1;
75
+ }
76
+ try {
77
+ const release = opt("release");
78
+ const r = newStory({ title, activity: opt("activity"), release: release === undefined ? undefined : Number(release), priority: opt("priority") }, opt("dir") ?? process.cwd());
79
+ console.log(`Added ${r.id} to release ${r.release}\nCreated ${r.feature}`);
80
+ console.log(`\nNext: write the user story and its scenarios in the feature file, then run nikos clear ${r.id}.`);
81
+ return 0;
82
+ }
83
+ catch (err) {
84
+ console.error(`✗ ${err.message}`);
85
+ return 1;
86
+ }
87
+ }
package/docs/format.md CHANGED
@@ -109,6 +109,10 @@ Rules:
109
109
  - Every story id has exactly one feature file and one `state.yaml` entry.
110
110
  - Release numbers start at 1 and have no gaps.
111
111
 
112
+ Add a story with `nikos story add "<title>" --activity <id>`. It takes the
113
+ next free id, adds it to the last release (or `--release <n>`), gives it a
114
+ `todo` entry in `state.yaml` and writes a starter feature file to fill in.
115
+
112
116
  ---
113
117
 
114
118
  ## features/&lt;activity&gt;/&lt;story-slug&gt;.feature
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nikos",
3
- "version": "0.0.2",
4
- "description": "nikOS \u2014 an open-source agent skill stack for spec-driven development. Specs, plans and Cucumber features live in your repo.",
3
+ "version": "0.0.4",
4
+ "description": "Spec-driven development for AI coding agents: story maps, Gherkin acceptance criteria and a done gate for Claude Code, Codex and Cursor",
5
5
  "license": "MIT",
6
6
  "author": "Niko Vijay",
7
7
  "homepage": "https://nikos.tools",
@@ -42,5 +42,19 @@
42
42
  "@cucumber/messages": "^26.0.1",
43
43
  "ajv": "^8.17.1",
44
44
  "yaml": "^2.6.1"
45
- }
45
+ },
46
+ "keywords": [
47
+ "spec-driven-development",
48
+ "ai-coding-agents",
49
+ "agentic-coding",
50
+ "claude-code",
51
+ "codex",
52
+ "cursor",
53
+ "bdd",
54
+ "gherkin",
55
+ "cucumber",
56
+ "acceptance-criteria",
57
+ "user-story-mapping",
58
+ "agent-skills"
59
+ ]
46
60
  }
@@ -35,7 +35,22 @@ Read the story's `.feature` file, `intent/constitution.md` and
35
35
  outreach to real people or accessibility, a scenario or the description
36
36
  covers it. A story that touches none of these passes and says so.
37
37
 
38
- ## 3. Report all five, in this order
38
+ ## 3. Fill small gaps, ask about big ones
39
+
40
+ Every spec leaves some rules unstated. Sort each gap you find:
41
+
42
+ - **Decide it** when it's a small rule any reasonable person would accept
43
+ and it's cheap to change later: how a tip rounds, the order a list sorts
44
+ in, the wording of a message. Pick the obvious choice, record it with
45
+ `nikos decide "<the rule>" --story <story-id> --why "<why this choice>"`,
46
+ and list it in the report. Recorded, it doesn't fail Clear.
47
+ - **Stop and ask** when the gap affects users' money, personal data, access
48
+ (who can see or change what), safety, or messages to real people. These are
49
+ product decisions, not yours. Clear fails until the person answers; record
50
+ their answer with `nikos decide`.
51
+ - **If you're not sure which it is, ask.**
52
+
53
+ ## 4. Report all five, in this order
39
54
 
40
55
  One line per check, every time, even when it passes:
41
56
 
@@ -47,9 +62,11 @@ Accurate: pass. <why>
47
62
  Responsible: pass. <why>
48
63
  ```
49
64
 
50
- Every fail names what's missing and the fix.
65
+ Every fail names what's missing and the fix. After the five lines, list
66
+ any rules you decided (`Decided: <rule>`) and any questions that block
67
+ building (`Ask: <question>`).
51
68
 
52
- ## 4. Stop on a fail
69
+ ## 5. Stop on a fail
53
70
 
54
71
  If any check fails, don't plan or build the story. Report the failures and
55
72
  the fixes, and wait for the spec to change.