nikos 0.0.1 → 0.0.3
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 +89 -17
- package/dist/cli.js +4 -0
- package/dist/commands/decide.js +2 -2
- package/dist/commands/story.js +87 -0
- package/dist/commands/verify.js +7 -3
- package/docs/format.md +7 -1
- package/docs/quickstart.md +153 -0
- package/package.json +1 -1
- package/skills/clear/SKILL.md +20 -3
- package/skills/decisions/SKILL.md +1 -1
- package/skills/nikos-mode/playbooks/build-a-story.md +2 -2
- package/templates/intent/decisions.md +1 -1
package/README.md
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
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
|
+
AI coding agents are quick to say "done". nikOS makes them prove it. Your
|
|
8
|
+
story map, plans and acceptance criteria live in your repo as plain files,
|
|
9
|
+
each criterion is a Cucumber scenario that runs as a test, and a story can't
|
|
10
|
+
be marked done until its scenarios pass. Your agent follows a named playbook
|
|
11
|
+
for each request instead of improvising, and every decision is written down
|
|
12
|
+
where the next person (or agent) can find it.
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
files and executable Cucumber features, and the agent can't call a story done
|
|
13
|
-
until its scenarios pass.
|
|
14
|
+
> Early work in progress. Open source, MIT licensed.
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
## Try it in 10 minutes
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
[docs/quickstart.md](docs/quickstart.md) takes an empty repo to one passing
|
|
19
|
+
scenario, with every command and the output you should see.
|
|
20
|
+
|
|
21
|
+
## Install in your agent tool
|
|
18
22
|
|
|
19
23
|
| Tool | Command |
|
|
20
24
|
| --- | --- |
|
|
@@ -23,11 +27,79 @@ until its scenarios pass.
|
|
|
23
27
|
| Cursor | Add `https://github.com/nikovijay/NikOS` as a plugin marketplace |
|
|
24
28
|
|
|
25
29
|
Then ask your agent to plan or build a story: the `nikos-mode` skill takes it
|
|
26
|
-
from there.
|
|
30
|
+
from there. The skills run the `nikos` command through `npx`, so there's
|
|
31
|
+
nothing else to install. More in [docs/install.md](docs/install.md).
|
|
32
|
+
|
|
33
|
+
## The loop
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx -y nikos init # create intent/ with a starter story
|
|
37
|
+
# write the story's scenarios in intent/features/
|
|
38
|
+
npx -y nikos verify setup # add a Cucumber runner for your stack
|
|
39
|
+
npx -y nikos verify TC-1 # run the story: it fails until it's built
|
|
40
|
+
# build it, then
|
|
41
|
+
npx -y nikos state set TC-1 done # refused unless its scenarios pass
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
With an agent, you only write the request ("add a story for X and build
|
|
45
|
+
it"); the playbooks run these steps and stop at each point that needs you.
|
|
46
|
+
|
|
47
|
+
## What lives in your repo
|
|
48
|
+
|
|
49
|
+
```text
|
|
50
|
+
intent/
|
|
51
|
+
├── constitution.md who it's for, the problem, what proves it works
|
|
52
|
+
├── storymap.yaml activities, releases, and the order to build stories in
|
|
53
|
+
├── state.yaml each story's status: todo, in_progress, needs_rework, done
|
|
54
|
+
├── decisions.md why things are the way they are, append-only
|
|
55
|
+
├── features/ one .feature file per story; its scenarios are the criteria
|
|
56
|
+
│ └── <activity>/<story>.feature
|
|
57
|
+
├── plans/ how each story will be built, versioned: <story>.v<N>.md
|
|
58
|
+
└── bets/ time-boxed bets and the stories that test them
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Every file is plain text you can read, review and diff. The full format:
|
|
62
|
+
[docs/format.md](docs/format.md). A worked example:
|
|
63
|
+
[examples/hello-todo](examples/hello-todo/intent).
|
|
64
|
+
|
|
65
|
+
## Commands
|
|
66
|
+
|
|
67
|
+
| Command | What it does |
|
|
68
|
+
| --- | --- |
|
|
69
|
+
| `nikos init` | Create `intent/` in this repo |
|
|
70
|
+
| `nikos validate` | Check `intent/` is well formed |
|
|
71
|
+
| `nikos story add "<title>"` | Add a story to the map, state and a starter feature |
|
|
72
|
+
| `nikos clear <id>` | Check a story is ready to build |
|
|
73
|
+
| `nikos plan new <id>` | Start the next version of a story's plan |
|
|
74
|
+
| `nikos verify setup` | Add a Cucumber runner for this repo's stack |
|
|
75
|
+
| `nikos verify [id]` | Run a story's scenarios, or all of them |
|
|
76
|
+
| `nikos state set <id> <status>` | Move a story; done only if its scenarios pass |
|
|
77
|
+
| `nikos next` | Print the next story to build |
|
|
78
|
+
| `nikos show <id>` | Summarise a story: feature, status, plan, bets, decisions |
|
|
79
|
+
| `nikos decide "<title>"` | Append a decision to `decisions.md` |
|
|
80
|
+
| `nikos decisions "<q>"` | Quote the decisions about a topic or story |
|
|
81
|
+
| `nikos bet new <id>` | Write a time-boxed bet card linked to its stories |
|
|
82
|
+
|
|
83
|
+
## Working with a team: intentdocs
|
|
84
|
+
|
|
85
|
+
When you want a visual story map, reviews and a team around the same specs,
|
|
86
|
+
[intentdocs](https://www.intentdocs.com) reads the same `intent/` folder:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npx intentdocs push # upload intent/ and link this repo
|
|
90
|
+
npx intentdocs pull # bring web edits back, shown as diffs first
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The repo stays the source of truth for specs and plans; intentdocs runs the
|
|
94
|
+
build and acceptance steps. What maps to what:
|
|
95
|
+
[docs/intentdocs-mapping.md](docs/intentdocs-mapping.md).
|
|
96
|
+
|
|
97
|
+
## Links
|
|
27
98
|
|
|
28
99
|
- Home: https://nikos.tools
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
100
|
+
- Quickstart: [docs/quickstart.md](docs/quickstart.md)
|
|
101
|
+
- Install: [docs/install.md](docs/install.md)
|
|
102
|
+
- Format: [docs/format.md](docs/format.md)
|
|
103
|
+
- Writing stories as features: [docs/gherkin.md](docs/gherkin.md)
|
|
32
104
|
|
|
33
105
|
MIT licensed.
|
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
|
package/dist/commands/decide.js
CHANGED
|
@@ -2,7 +2,7 @@ import { appendFileSync, existsSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { STORY_ID, findIntentDir, read } from "../lib/project.js";
|
|
4
4
|
import { formatResult, validate } from "./validate.js";
|
|
5
|
-
const CATEGORIES = ["product", "architecture", "design", "process"];
|
|
5
|
+
const CATEGORIES = ["product", "architecture", "design", "interface", "process"];
|
|
6
6
|
/** Append a dated entry to decisions.md. Earlier entries are never touched. */
|
|
7
7
|
export function decide(input, dir) {
|
|
8
8
|
const title = input.title.trim();
|
|
@@ -39,7 +39,7 @@ export function runDecide(args) {
|
|
|
39
39
|
return i >= 0 ? rest[i + 1] : undefined;
|
|
40
40
|
};
|
|
41
41
|
if (!title || title.startsWith("--")) {
|
|
42
|
-
console.error('Usage: nikos decide "<title>" --why "<why>" [--story <id>] [--category product|architecture|design|process] [--supersedes "<title>"]');
|
|
42
|
+
console.error('Usage: nikos decide "<title>" --why "<why>" [--story <id>] [--category product|architecture|design|interface|process] [--supersedes "<title>"]');
|
|
43
43
|
return 1;
|
|
44
44
|
}
|
|
45
45
|
const dir = opt("dir") ?? process.cwd();
|
|
@@ -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/dist/commands/verify.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { copyFileSync, existsSync, mkdirSync } from "node:fs";
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join, relative } from "node:path";
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
@@ -12,6 +12,8 @@ const INSTALL = {
|
|
|
12
12
|
ts: "npm install --save-dev @cucumber/cucumber@^12 tsx",
|
|
13
13
|
python: "pip install behave",
|
|
14
14
|
};
|
|
15
|
+
/** A step only the untouched example story has. */
|
|
16
|
+
const EXAMPLE_STEP = "When I do the one thing this product is for";
|
|
15
17
|
const CONFIG = { js: "cucumber.mjs", ts: "cucumber.mjs", python: "behave.ini" };
|
|
16
18
|
function stackOrThrow(root) {
|
|
17
19
|
const stack = detectStack(root);
|
|
@@ -25,8 +27,10 @@ export function setupVerify(root) {
|
|
|
25
27
|
const src = join(templateDir, stack === "python" ? "python" : stack);
|
|
26
28
|
const created = [];
|
|
27
29
|
const skipped = [];
|
|
28
|
-
// Step stubs are for the example story `nikos init` creates; skip them once
|
|
29
|
-
|
|
30
|
+
// Step stubs are for the example story `nikos init` creates; skip them once
|
|
31
|
+
// it's gone or rewritten into a real story, whose steps they wouldn't match.
|
|
32
|
+
const example = join(root, "intent/features/getting-started/first-story.feature");
|
|
33
|
+
const hasExample = existsSync(example) && readFileSync(example, "utf8").includes(EXAMPLE_STEP);
|
|
30
34
|
for (const file of walk(src)) {
|
|
31
35
|
const rel = relative(src, file);
|
|
32
36
|
if (!hasExample && /first[-_]story/.test(rel))
|
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
|
|
@@ -195,7 +199,9 @@ Why this, and what we rejected.
|
|
|
195
199
|
|
|
196
200
|
The heading is `date — title (story id)`; the story id is optional for
|
|
197
201
|
project-wide decisions. Category is one of `product`, `architecture`,
|
|
198
|
-
`design`, `process`.
|
|
202
|
+
`design`, `interface`, `process`. Use `interface` for a choice other stories
|
|
203
|
+
must keep to: a command's syntax, an output format, an API's shape. The
|
|
204
|
+
build-a-story playbook reads these before planning.
|
|
199
205
|
|
|
200
206
|
---
|
|
201
207
|
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Quickstart: your first passing scenario in 10 minutes
|
|
2
|
+
|
|
3
|
+
You'll make a tiny tip calculator in an empty repo and finish with one story
|
|
4
|
+
whose acceptance criterion runs as a test and passes. You need Node 20 or
|
|
5
|
+
later and npm. Nothing is installed globally: every `nikos` command runs
|
|
6
|
+
through `npx`.
|
|
7
|
+
|
|
8
|
+
## 1. Make a repo (1 minute)
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
mkdir tip-calculator && cd tip-calculator
|
|
12
|
+
git init
|
|
13
|
+
npm init -y
|
|
14
|
+
npm pkg set type=module
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## 2. Create the intent/ folder (1 minute)
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx -y nikos init --name "Tip Calculator" --key TC
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
You should see:
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
Created intent/ (6 files)
|
|
27
|
+
✓ intent/ is valid — 1 story, 1 release
|
|
28
|
+
|
|
29
|
+
Next: fill in intent/constitution.md, then write your first story in intent/features/.
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`intent/` now holds your story map, a state file, a decision log and one
|
|
33
|
+
starter story, TC-1. [docs/format.md](format.md) explains each file.
|
|
34
|
+
|
|
35
|
+
## 3. Write the story (2 minutes)
|
|
36
|
+
|
|
37
|
+
Replace the starter story with a real one. Save this as
|
|
38
|
+
**`intent/features/getting-started/first-story.feature`**:
|
|
39
|
+
|
|
40
|
+
```gherkin
|
|
41
|
+
@TC-1 @release-1 @must @activity-getting-started
|
|
42
|
+
Feature: Add a tip to the bill
|
|
43
|
+
|
|
44
|
+
As a diner
|
|
45
|
+
I want to add a tip to my bill
|
|
46
|
+
So that I know what to pay
|
|
47
|
+
|
|
48
|
+
@AC1 @must
|
|
49
|
+
Scenario: AC1 — A 20% tip on a 50 bill
|
|
50
|
+
Given a bill of 50
|
|
51
|
+
When I add a 20% tip
|
|
52
|
+
Then the total is 60.00
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The scenario is the acceptance criterion: TC-1 isn't done until it passes.
|
|
56
|
+
Check the folder is still well formed, and start the story:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npx -y nikos validate
|
|
60
|
+
npx -y nikos state set TC-1 in_progress
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
✓ intent/ is valid — 1 story, 1 release
|
|
65
|
+
TC-1: todo → in_progress
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 4. Set up the runner (2 minutes)
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npx -y nikos verify setup
|
|
72
|
+
npm install --save-dev @cucumber/cucumber@^12
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`verify setup` notices this is a JavaScript repo and adds `cucumber.mjs`,
|
|
76
|
+
which runs every story in `intent/` as a test. Now run TC-1:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npx -y nikos verify TC-1
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
It fails, and says why: none of the three steps has any code behind it yet.
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
1 scenario (1 undefined)
|
|
86
|
+
3 steps (3 undefined)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## 5. Make it pass (3 minutes)
|
|
90
|
+
|
|
91
|
+
Write the code. Save this as **`tip.js`**:
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
export function totalWithTip(bill, percent) {
|
|
95
|
+
return (bill * (1 + percent / 100)).toFixed(2);
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Connect each step to the code. Save this as **`test/steps/tip.steps.mjs`**:
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
import { Given, When, Then } from "@cucumber/cucumber";
|
|
103
|
+
import assert from "node:assert/strict";
|
|
104
|
+
import { totalWithTip } from "../../tip.js";
|
|
105
|
+
|
|
106
|
+
Given("a bill of {int}", function (bill) {
|
|
107
|
+
this.bill = bill;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
When("I add a {int}% tip", function (percent) {
|
|
111
|
+
this.total = totalWithTip(this.bill, percent);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
Then("the total is {float}", function (expected) {
|
|
115
|
+
assert.equal(this.total, expected.toFixed(2));
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Run it again:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npx -y nikos verify TC-1
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
```text
|
|
126
|
+
1 scenario (1 passed)
|
|
127
|
+
3 steps (3 passed)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## 6. Call it done (1 minute)
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
npx -y nikos state set TC-1 done
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
```text
|
|
137
|
+
TC-1: in_progress → done
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`state set … done` runs the scenarios first and refuses if any fail, so a
|
|
141
|
+
story can't be marked done on the agent's word alone.
|
|
142
|
+
|
|
143
|
+
That's the whole loop: write the story, run it, make it pass, mark it done.
|
|
144
|
+
|
|
145
|
+
## Next: let your agent do it
|
|
146
|
+
|
|
147
|
+
Install nikOS in your agent tool ([docs/install.md](install.md)), then ask:
|
|
148
|
+
|
|
149
|
+
> Add a story for splitting the bill between people, and build it.
|
|
150
|
+
|
|
151
|
+
The `nikos-mode` skill picks the right playbook: it writes the story,
|
|
152
|
+
checks it, plans it, builds it, and stops at "done" only when the new
|
|
153
|
+
scenario passes.
|
package/package.json
CHANGED
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.
|
|
@@ -38,7 +38,7 @@ add what the git history of `intent/` shows (`git log -p -- intent/`), marked
|
|
|
38
38
|
clearly as history, not a recorded decision. Then offer to log one:
|
|
39
39
|
|
|
40
40
|
```
|
|
41
|
-
nikos decide "<title>" --why "<why>" --category <product|architecture|design|process>
|
|
41
|
+
nikos decide "<title>" --why "<why>" --category <product|architecture|design|interface|process>
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
Log it only once the person tells you the reason. Don't write a reason for
|
|
@@ -5,10 +5,10 @@ scenarios pass, not that you say so.
|
|
|
5
5
|
|
|
6
6
|
1. Run `nikos next` to pick the story, or use the story id the person named.
|
|
7
7
|
2. Check the story with the clear skill, and stop if any of the five checks fails.
|
|
8
|
-
3. Run `nikos plan new <story-id>` and write the approach into the plan file it creates.
|
|
8
|
+
3. Run `nikos decisions interface`, then `nikos plan new <story-id>` and write the approach into the plan file it creates. Reuse every interface decision the story touches, and cite each in the step that uses it as `Reuses: <decision title> (intent/decisions.md:<lines>)`. If the story needs to change one, stop and ask.
|
|
9
9
|
4. Run `nikos state set <story-id> in_progress`.
|
|
10
10
|
5. Implement the story, with a step definition for each step in its scenarios (see the verify skill).
|
|
11
11
|
6. Run `nikos verify <story-id>`, then `nikos verify` to check every other story still passes, and keep the output.
|
|
12
|
-
7. Record each decision you made with `nikos decide "<title>" --story <story-id> --why "<why>"`.
|
|
12
|
+
7. Record each decision you made with `nikos decide "<title>" --story <story-id> --why "<why>"`. Add `--category interface` for anything a later story must keep to: command syntax, output format, API shape.
|
|
13
13
|
8. Run `nikos state set <story-id> done`. If it refuses, fix the failing scenarios and go back to step 6.
|
|
14
14
|
9. Report every step with its evidence or its skip reason.
|