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 CHANGED
@@ -1,20 +1,24 @@
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
+ 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
- Your story map, plans and acceptance criteria live in your repo as plain
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
- > Early work in progress. Start with [the intent/ format](docs/format.md).
16
+ ## Try it in 10 minutes
16
17
 
17
- ## Install
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. Details and the formats each tool expects: [docs/install.md](docs/install.md).
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
- - Format spec: [docs/format.md](docs/format.md)
30
- - Mapping to intentdocs: [docs/intentdocs-mapping.md](docs/intentdocs-mapping.md)
31
- - Worked example: [examples/hello-todo](examples/hello-todo/intent)
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
@@ -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
+ }
@@ -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 it's gone.
29
- const hasExample = existsSync(join(root, "intent/features/getting-started/first-story.feature"));
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/&lt;activity&gt;/&lt;story-slug&gt;.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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nikos",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "nikOS \u2014 an open-source agent skill stack for spec-driven development. Specs, plans and Cucumber features live in your repo.",
5
5
  "license": "MIT",
6
6
  "author": "Niko Vijay",
@@ -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.
@@ -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.
@@ -5,7 +5,7 @@ one that supersedes it.
5
5
 
6
6
  <!--
7
7
  ## YYYY-MM-DD — Decision title (STORY-ID)
8
- **Category:** product | architecture | design | process
8
+ **Category:** product | architecture | design | interface | process
9
9
  **Supersedes:** none
10
10
 
11
11
  Why this, and what we rejected.