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 +18 -22
- package/dist/cli.js +4 -0
- package/dist/commands/story.js +87 -0
- package/docs/format.md +4 -0
- package/package.json +17 -3
- package/skills/clear/SKILL.md +20 -3
package/README.md
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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/<activity>/<story-slug>.feature
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nikos",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
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
|
}
|
package/skills/clear/SKILL.md
CHANGED
|
@@ -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.
|
|
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
|
-
##
|
|
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.
|