nikos 0.0.2 → 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 +5 -8
- package/dist/cli.js +4 -0
- package/dist/commands/decide.js +2 -2
- package/dist/commands/story.js +87 -0
- package/docs/format.md +7 -1
- 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,12 +1,8 @@
|
|
|
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>
|
|
10
6
|
|
|
11
7
|
AI coding agents are quick to say "done". nikOS makes them prove it. Your
|
|
12
8
|
story map, plans and acceptance criteria live in your repo as plain files,
|
|
@@ -72,6 +68,7 @@ Every file is plain text you can read, review and diff. The full format:
|
|
|
72
68
|
| --- | --- |
|
|
73
69
|
| `nikos init` | Create `intent/` in this repo |
|
|
74
70
|
| `nikos validate` | Check `intent/` is well formed |
|
|
71
|
+
| `nikos story add "<title>"` | Add a story to the map, state and a starter feature |
|
|
75
72
|
| `nikos clear <id>` | Check a story is ready to build |
|
|
76
73
|
| `nikos plan new <id>` | Start the next version of a story's plan |
|
|
77
74
|
| `nikos verify setup` | Add a Cucumber runner for this repo's stack |
|
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/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
|
|
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.
|