nikos 0.1.1 → 0.2.1
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 +46 -9
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +49 -1
- package/dist/commands/approve.d.ts +16 -0
- package/dist/commands/approve.js +120 -0
- package/dist/commands/ci.d.ts +19 -0
- package/dist/commands/ci.js +174 -0
- package/dist/commands/clear.d.ts +14 -0
- package/dist/commands/decide.d.ts +11 -0
- package/dist/commands/decide.js +3 -3
- package/dist/commands/decisions.d.ts +14 -0
- package/dist/commands/decisions.js +2 -2
- package/dist/commands/doctor.d.ts +11 -0
- package/dist/commands/doctor.js +92 -0
- package/dist/commands/export.d.ts +48 -0
- package/dist/commands/export.js +164 -0
- package/dist/commands/init.d.ts +16 -0
- package/dist/commands/init.js +8 -12
- package/dist/commands/join.d.ts +17 -0
- package/dist/commands/join.js +91 -0
- package/dist/commands/map.d.ts +65 -0
- package/dist/commands/map.js +202 -25
- package/dist/commands/migrate.d.ts +10 -0
- package/dist/commands/migrate.js +105 -0
- package/dist/commands/next.d.ts +19 -0
- package/dist/commands/next.js +29 -5
- package/dist/commands/plan.d.ts +8 -0
- package/dist/commands/plan.js +9 -8
- package/dist/commands/release.d.ts +66 -0
- package/dist/commands/release.js +75 -6
- package/dist/commands/show.d.ts +3 -0
- package/dist/commands/show.js +26 -4
- package/dist/commands/start.d.ts +28 -0
- package/dist/commands/start.js +138 -0
- package/dist/commands/state.d.ts +27 -0
- package/dist/commands/state.js +40 -22
- package/dist/commands/step.d.ts +17 -0
- package/dist/commands/step.js +38 -2
- package/dist/commands/story.d.ts +21 -0
- package/dist/commands/story.js +7 -10
- package/dist/commands/suggest.d.ts +16 -0
- package/dist/commands/suggest.js +140 -0
- package/dist/commands/tracker.d.ts +60 -0
- package/dist/commands/tracker.js +185 -0
- package/dist/commands/validate.d.ts +15 -0
- package/dist/commands/validate.js +149 -14
- package/dist/commands/verify.d.ts +20 -0
- package/dist/commands/video.d.ts +33 -0
- package/dist/commands/video.js +4 -3
- package/dist/index.d.ts +20 -0
- package/dist/index.js +24 -0
- package/dist/lib/approvals.d.ts +29 -0
- package/dist/lib/approvals.js +28 -0
- package/dist/lib/bets.d.ts +21 -0
- package/dist/lib/bets.js +9 -2
- package/dist/lib/frontmatter.d.ts +5 -0
- package/dist/lib/project.d.ts +25 -0
- package/dist/lib/project.js +25 -7
- package/dist/lib/settings.d.ts +29 -0
- package/dist/lib/settings.js +110 -0
- package/dist/lib/state-files.d.ts +44 -0
- package/dist/lib/state-files.js +102 -0
- package/dist/lib/store.d.ts +44 -0
- package/dist/lib/store.js +97 -0
- package/dist/lint/constitution.d.ts +5 -0
- package/dist/lint/gherkin.d.ts +12 -0
- package/dist/lint/gherkin.js +2 -2
- package/dist/lint/portable.d.ts +10 -0
- package/dist/mcp/server.d.ts +4 -0
- package/dist/mcp/server.js +239 -0
- package/dist/planning/bets.d.ts +31 -0
- package/dist/planning/cli.d.ts +8 -0
- package/dist/planning/cli.js +17 -1
- package/dist/planning/delivery.d.ts +12 -0
- package/dist/planning/delivery.js +8 -5
- package/dist/planning/krs.d.ts +28 -0
- package/dist/planning/model.d.ts +55 -0
- package/dist/planning/model.js +10 -10
- package/dist/planning/rollout.d.ts +72 -0
- package/dist/planning/rollout.js +206 -0
- package/dist/planning/server.d.ts +113 -0
- package/dist/planning/server.js +9 -6
- package/dist/planning/ui.d.ts +7 -0
- package/dist/planning/ui.js +26 -1
- package/dist/planning/upstream.d.ts +73 -0
- package/dist/planning/upstream.js +78 -20
- package/dist/verify/detect.d.ts +3 -0
- package/docs/format.md +110 -1
- package/docs/gherkin.md +3 -0
- package/docs/install.md +62 -0
- package/docs/library.md +53 -0
- package/docs/planning.md +51 -5
- package/docs/settings.md +112 -0
- package/package.json +12 -2
- package/schemas/approvals.schema.json +27 -0
- package/schemas/planning/kr.schema.json +1 -1
- package/schemas/settings.schema.json +40 -0
- package/schemas/state.schema.json +6 -2
- package/schemas/storymap.schema.json +13 -0
- package/skills/nikos-mode/SKILL.md +22 -1
- package/skills/nikos-mode/playbooks/build-a-story.md +1 -1
- package/skills/nikos-mode/playbooks/company.md +16 -0
- package/skills/nikos-mode/playbooks/index.md +4 -0
- package/skills/nikos-mode/playbooks/map-existing.md +14 -0
- package/skills/nikos-mode/playbooks/start.md +12 -0
- package/skills/nikos-mode/playbooks/tracker.md +14 -0
- package/skills/nikos-mode/trackers/github.md +18 -0
- package/skills/nikos-mode/trackers/gitlab.md +17 -0
- package/skills/nikos-mode/trackers/jira.md +19 -0
- package/skills/nikos-mode/trackers/linear.md +17 -0
- package/skills/verify/SKILL.md +16 -0
package/docs/format.md
CHANGED
|
@@ -17,6 +17,9 @@ intent/
|
|
|
17
17
|
<story-id>.v<N>.md versioned implementation plans
|
|
18
18
|
decisions.md append-only decision log
|
|
19
19
|
state.yaml status, blockers and the run queue
|
|
20
|
+
state/<story-id>.yaml optional: each story's state in its own file
|
|
21
|
+
approvals.yaml optional: people's sign-offs on done stories
|
|
22
|
+
approvals/<story-id>.yaml optional: each sign-off in its own file
|
|
20
23
|
outreach/ optional, drafts from the validate playbook
|
|
21
24
|
<date>-<channel>.md
|
|
22
25
|
```
|
|
@@ -89,6 +92,10 @@ themselves live in feature files.
|
|
|
89
92
|
format_version: 1
|
|
90
93
|
project: hello-todo
|
|
91
94
|
key: HT # story id prefix: HT-1, HT-2 …
|
|
95
|
+
personas: # optional: who the product is for; a story tags them @persona-<id>
|
|
96
|
+
- id: busy
|
|
97
|
+
title: Busy person
|
|
98
|
+
description: Juggles work and home; opens the app for seconds at a time. # optional
|
|
92
99
|
activities:
|
|
93
100
|
- id: tasks # slug, matches the features/<activity>/ folder
|
|
94
101
|
title: Manage tasks
|
|
@@ -117,6 +124,11 @@ Rules:
|
|
|
117
124
|
step on the map.
|
|
118
125
|
- A story's `@release-N` tag should match the release that lists it;
|
|
119
126
|
`nikos validate` warns when they differ.
|
|
127
|
+
- Persona ids are unique, and a story's `@persona-<id>` must name one.
|
|
128
|
+
- A story's `@depends-<id>` must name another story, and dependencies can't
|
|
129
|
+
form a loop. `nikos validate` warns when a story depends on one in a later
|
|
130
|
+
release, or on a cancelled one. `nikos next` won't pick a story until
|
|
131
|
+
everything it depends on is done.
|
|
120
132
|
|
|
121
133
|
### Bet links (optional)
|
|
122
134
|
|
|
@@ -155,6 +167,13 @@ keeps comments and leaves the rest of the file as it was:
|
|
|
155
167
|
| `nikos release move <story-id> <n>` | Move a story to the end of another release and retag its feature |
|
|
156
168
|
| `nikos release reorder <n> <position>` | Move a release, renumber them all from 1 and retag every moved story |
|
|
157
169
|
| `nikos release remove <n>` | Remove a release with no stories or bets, renumber the rest and retag their stories |
|
|
170
|
+
| `nikos release check <n> [--run]` | Say whether a release is ready to ship and list what's in the way (`--run` reruns each done story's scenarios); exits 1 when it isn't |
|
|
171
|
+
|
|
172
|
+
`nikos export --format prd|linear|jira` writes the map in another tool's shape:
|
|
173
|
+
a PRD in Markdown, Linear issues (releases become milestones), or the body of
|
|
174
|
+
Jira's bulk create (`POST /rest/api/2/issue/bulk`). `--release <n>` exports one
|
|
175
|
+
release; `--project-key` sets Jira's project (default: the map's key). The
|
|
176
|
+
exports are copies: keep editing `intent/`.
|
|
158
177
|
|
|
159
178
|
---
|
|
160
179
|
|
|
@@ -259,7 +278,9 @@ blockers, and the run queue. Kept apart so specs stay clean.
|
|
|
259
278
|
format_version: 1
|
|
260
279
|
stories:
|
|
261
280
|
HT-1:
|
|
262
|
-
status: done # todo | in_progress | needs_rework | done
|
|
281
|
+
status: done # todo | in_progress | needs_rework | done | cancelled
|
|
282
|
+
spec_hash: 3f9a1c2b7d4e # set at done: a fingerprint of the feature from Feature: on
|
|
283
|
+
done_at: 2026-11-03T14:02:11Z # set at done
|
|
263
284
|
claimed_by: claude-code # optional
|
|
264
285
|
plan_version: 2 # optional, the plan in use
|
|
265
286
|
blockers: [] # optional, plain sentences
|
|
@@ -274,6 +295,94 @@ Status moves `todo → in_progress → done`, or `in_progress → needs_rework
|
|
|
274
295
|
in_progress`. Only a passing verification can set `done` (see the
|
|
275
296
|
build-a-story playbook).
|
|
276
297
|
|
|
298
|
+
- **Approval** isn't a status. It's a person's sign-off, kept in
|
|
299
|
+
[approvals.yaml](#approvalsyaml) so a repo can protect it.
|
|
300
|
+
- **Cancelled** keeps a story you've decided not to build, with a reason:
|
|
301
|
+
`nikos state set <id> cancelled --reason "<why>"` from todo, in_progress or
|
|
302
|
+
needs_rework. It's saved as `cancelled_reason`. `cancelled → todo` brings it back.
|
|
303
|
+
Cancelled stories don't count towards a release or a bet, and `nikos next` skips them.
|
|
304
|
+
- **Spec changed:** `spec_hash` records what the scenarios proved. If the feature
|
|
305
|
+
changes after done or approved, `nikos validate` warns, the map shows the
|
|
306
|
+
story as "spec changed", and `nikos release check` counts it as a gap. Tags
|
|
307
|
+
above `Feature:` aren't part of the fingerprint, so moving a story between
|
|
308
|
+
releases doesn't count as a change.
|
|
309
|
+
|
|
310
|
+
### One file per story
|
|
311
|
+
|
|
312
|
+
With several people or agents working in one repo, two branches that move
|
|
313
|
+
different stories both edit `state.yaml`, and one of them has to resolve a
|
|
314
|
+
merge conflict. `nikos migrate per-story` gives every story its own file:
|
|
315
|
+
|
|
316
|
+
```
|
|
317
|
+
intent/state.yaml format_version and the run queue; stories: {}
|
|
318
|
+
intent/state/HT-1.yaml HT-1's entry, the same fields as under stories:
|
|
319
|
+
intent/approvals/HT-1.yaml HT-1's sign-off, the same fields as under approvals:
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Once `intent/state/` exists, every command reads and writes there: a new
|
|
323
|
+
story gets its own file, and an approval goes to `intent/approvals/<id>.yaml`.
|
|
324
|
+
`nikos validate` checks each file, and reports a story kept in both places.
|
|
325
|
+
The migration only runs on a repo that validates, and it carries a CODEOWNERS
|
|
326
|
+
rule on `approvals.yaml` over to `approvals/`. `nikos migrate single-file`
|
|
327
|
+
moves everything back. Commit a migration on its own, from a branch nobody else
|
|
328
|
+
is moving stories on.
|
|
329
|
+
|
|
330
|
+
## approvals.yaml
|
|
331
|
+
|
|
332
|
+
**Purpose:** people's sign-offs on done stories. It's kept apart from
|
|
333
|
+
`state.yaml`, which agents write all the time, so it can be protected.
|
|
334
|
+
|
|
335
|
+
**Path:** `intent/approvals.yaml` (optional; the first approval creates it)
|
|
336
|
+
|
|
337
|
+
**Format:** YAML.
|
|
338
|
+
|
|
339
|
+
```yaml
|
|
340
|
+
format_version: 1
|
|
341
|
+
approvals:
|
|
342
|
+
HT-1:
|
|
343
|
+
by: Niko Vijay # git's user.name
|
|
344
|
+
on: 2026-11-06
|
|
345
|
+
via: chat # app, chat or terminal: how it was given
|
|
346
|
+
spec_hash: 3f9a1c2b7d4e # the feature that was approved
|
|
347
|
+
done_at: 2026-11-03T14:02:11Z # the done that was approved
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
`nikos approve <id>` writes an entry, in one of three ways:
|
|
351
|
+
|
|
352
|
+
- **Through the app.** With `nikos mcp`, the approve tool asks the person
|
|
353
|
+
directly through their app's own prompt. The model never sees the question
|
|
354
|
+
or gives the answer. Recorded as `via: app`.
|
|
355
|
+
|
|
356
|
+
- **In chat.** When a story reaches done, the agent asks "Do you want to
|
|
357
|
+
approve HT-1: Add a task?". Only if the person answers Approve does it run
|
|
358
|
+
`nikos approve HT-1 --via chat`. The playbooks never let an agent approve on
|
|
359
|
+
its own judgement, and `via: chat` records how the approval was given.
|
|
360
|
+
- **In a terminal.** The person runs `nikos approve HT-1` and types the id
|
|
361
|
+
back. It refuses to run without an interactive terminal, which is how coding
|
|
362
|
+
agents run commands.
|
|
363
|
+
|
|
364
|
+
Either way, it approves only a done story whose feature hasn't changed since,
|
|
365
|
+
and reruns its scenarios first.
|
|
366
|
+
|
|
367
|
+
An approval holds while the story stays done, since that same done, with the
|
|
368
|
+
same feature. Move it to needs_rework, take it through done again, or change its
|
|
369
|
+
feature, and the approval lapses: `nikos validate` warns, and the map shows the
|
|
370
|
+
story as done rather than approved. A person then approves it again.
|
|
371
|
+
|
|
372
|
+
On the map, in exports and in `nikos release check`, a story with an approval
|
|
373
|
+
that holds shows as **approved**, and counts as done.
|
|
374
|
+
|
|
375
|
+
**Make approvals need a review.** Protect the default branch and add a
|
|
376
|
+
`CODEOWNERS` rule for the file, so a change to it only merges when that person
|
|
377
|
+
approves the pull request (GitHub) or merge request (GitLab):
|
|
378
|
+
|
|
379
|
+
```
|
|
380
|
+
/intent/approvals.yaml @your-product-lead
|
|
381
|
+
/intent/approvals/ @your-product-lead # with one file per story
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
On GitLab, requiring code-owner approval needs a paid tier.
|
|
385
|
+
|
|
277
386
|
---
|
|
278
387
|
|
|
279
388
|
## outreach/<date>-<channel>.md (optional)
|
package/docs/gherkin.md
CHANGED
|
@@ -56,6 +56,9 @@ criterion's priority).
|
|
|
56
56
|
| `@activity-<id>` | activity, matching the folder name | no |
|
|
57
57
|
| `@step-<id>` | step within that activity, from `storymap.yaml` | no |
|
|
58
58
|
| `@effort-XS` … `@effort-XL` | size estimate | no |
|
|
59
|
+
| `@depends-<story-id>` | a story that must be done first; repeat for several | no |
|
|
60
|
+
| `@persona-<id>` | who the story is for, from `personas:` in `storymap.yaml`; repeatable | no |
|
|
61
|
+
| `@type-technical` / `@type-bug-risk` | a story that isn't a user story (the default) | no |
|
|
59
62
|
|
|
60
63
|
Anything else is a warning, so you notice typos.
|
|
61
64
|
|
package/docs/install.md
CHANGED
|
@@ -32,6 +32,10 @@ add the marketplace first, then install:
|
|
|
32
32
|
From a terminal, the same commands are `claude plugin marketplace add
|
|
33
33
|
nikovijay/NikOS` and `claude plugin install nikos@nikos`.
|
|
34
34
|
|
|
35
|
+
At the start of each session in a repo with a story map, the plugin's hook
|
|
36
|
+
(`hooks/hooks.json`) tells the agent where things stand and what to suggest
|
|
37
|
+
next.
|
|
38
|
+
|
|
35
39
|
## Codex
|
|
36
40
|
|
|
37
41
|
```
|
|
@@ -41,6 +45,14 @@ codex plugin marketplace add nikovijay/NikOS
|
|
|
41
45
|
Then install nikOS from the Plugins directory. Update it later with `codex
|
|
42
46
|
plugin marketplace upgrade`.
|
|
43
47
|
|
|
48
|
+
The plugin ships the same session-start hook as Claude Code
|
|
49
|
+
(`hooks/hooks.json`). Codex runs a plugin's hooks only after you review and
|
|
50
|
+
trust them, and asks again when they change. Until you trust it, nikOS works
|
|
51
|
+
without it: the agent runs `nikos suggest` itself after each request. Codex's
|
|
52
|
+
docs say plugin hooks run for plugins installed manually in the Codex desktop
|
|
53
|
+
app, and that plugins with hooks can't be listed in its public plugin
|
|
54
|
+
directory.
|
|
55
|
+
|
|
44
56
|
## Cursor
|
|
45
57
|
|
|
46
58
|
Cursor installs plugins from a marketplace in its settings rather than a
|
|
@@ -50,6 +62,11 @@ then Add Marketplace, then Import from Repo), and install nikOS from it.
|
|
|
50
62
|
To try it without a marketplace, copy the repo to
|
|
51
63
|
`~/.cursor/plugins/local/nikos` and run "Developer: Reload Window".
|
|
52
64
|
|
|
65
|
+
The plugin's `sessionStart` hook (`hooks/cursor-hooks.json`) adds where things
|
|
66
|
+
stand and what to suggest next to each new conversation, in a repo with a story
|
|
67
|
+
map. Cursor runs it without waiting, so on a slow first run (while `npx` fetches
|
|
68
|
+
nikos) it can land after your first message. It doesn't run in cloud agents.
|
|
69
|
+
|
|
53
70
|
## Packs
|
|
54
71
|
|
|
55
72
|
Packs are extra tools that install on their own, next to nikOS or without it.
|
|
@@ -65,6 +82,45 @@ In Codex and Cursor, install the pack from the same marketplace as nikOS.
|
|
|
65
82
|
nikOS's router hands requests to an installed pack's skill, and gives the
|
|
66
83
|
install command when the pack is missing.
|
|
67
84
|
|
|
85
|
+
## Any app that runs MCP servers
|
|
86
|
+
|
|
87
|
+
`nikos mcp` runs nikOS as a local MCP server. Claude Desktop, Cursor, Codex,
|
|
88
|
+
Claude Code and other apps that run MCP servers on your machine can use it, with
|
|
89
|
+
no skills to install. Its tools are nikOS's commands, its prompts are the
|
|
90
|
+
playbooks, and it tells the app's model to keep the conversation going with a
|
|
91
|
+
short menu of next steps. Point it at one repo with `--dir`.
|
|
92
|
+
|
|
93
|
+
**Claude Desktop**: in Settings → Developer → Edit Config, add to
|
|
94
|
+
`claude_desktop_config.json`:
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"mcpServers": {
|
|
99
|
+
"nikos": { "command": "npx", "args": ["-y", "nikos", "mcp", "--dir", "/path/to/your/repo"] }
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Cursor**: the same `mcpServers` entry, in `.cursor/mcp.json` in the repo.
|
|
105
|
+
|
|
106
|
+
**Codex**: in `~/.codex/config.toml`:
|
|
107
|
+
|
|
108
|
+
```toml
|
|
109
|
+
[mcp_servers.nikos]
|
|
110
|
+
command = "npx"
|
|
111
|
+
args = ["-y", "nikos", "mcp", "--dir", "/path/to/your/repo"]
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Claude Code**: `claude mcp add nikos -- npx -y nikos mcp --dir "$PWD"` from
|
|
115
|
+
the repo. The plugin above gives Claude Code the same and more, so you only
|
|
116
|
+
need one of the two.
|
|
117
|
+
|
|
118
|
+
**Approvals through the app.** When you ask to approve a story, the server asks
|
|
119
|
+
you directly through the app's own prompt ("Do you want to approve HT-2:
|
|
120
|
+
Reorder tasks?"). The model never sees the question or gives the answer, and
|
|
121
|
+
the approval is recorded as `via: app`. If an app can't ask you directly, the
|
|
122
|
+
server says so and approves nothing.
|
|
123
|
+
|
|
68
124
|
## The command-line tool
|
|
69
125
|
|
|
70
126
|
The skills run the `nikos` command. When it isn't installed, they run it
|
|
@@ -87,6 +143,12 @@ name fields again on 2026-11-05 (no page showed a version):
|
|
|
87
143
|
- Codex: https://developers.openai.com/plugins/build/plugins. The plugin
|
|
88
144
|
title is `interface.displayName` in `.codex-plugin/plugin.json`. The
|
|
89
145
|
marketplace title is the same field on `.agents/plugins/marketplace.json`.
|
|
146
|
+
- Hooks, read on 2026-11-06: Cursor's https://cursor.com/docs/agent/hooks
|
|
147
|
+
(`sessionStart`, JSON output with `additional_context`, `timeout` in seconds;
|
|
148
|
+
a plugin points at its hooks with `hooks` in `.cursor-plugin/plugin.json`),
|
|
149
|
+
and Codex's plugin page above and https://learn.chatgpt.com/docs/hooks
|
|
150
|
+
(`SessionStart`, the same format as Claude Code, plain stdout added as
|
|
151
|
+
context, hooks run only once trusted).
|
|
90
152
|
|
|
91
153
|
All three find skills as `skills/<name>/SKILL.md` at the plugin root.
|
|
92
154
|
`test/install.test.ts` checks the manifests agree with each other and with
|
package/docs/library.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Using nikOS as a library
|
|
2
|
+
|
|
3
|
+
The `nikos` package is also a library: the same rules the CLI follows, for
|
|
4
|
+
tools that work with a repo's files without a shell or a checkout. A web app,
|
|
5
|
+
a bot or a server can fetch a repo's files from GitHub or GitLab, run nikOS on
|
|
6
|
+
them in memory, and commit the files that changed.
|
|
7
|
+
|
|
8
|
+
```js
|
|
9
|
+
import { MemoryStore, withStore, validate, newStory, suggest } from "nikos";
|
|
10
|
+
|
|
11
|
+
// The repo's files, as your host's API hands them over: path → text.
|
|
12
|
+
const repo = new MemoryStore({
|
|
13
|
+
"intent/storymap.yaml": storymapText,
|
|
14
|
+
"intent/state.yaml": stateText,
|
|
15
|
+
// …every file under intent/ (and planning/, if the repo plans with nikOS)
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const changes = withStore(repo, () => {
|
|
19
|
+
newStory({ title: "Split the bill evenly", activity: "checkout" }, repo.root);
|
|
20
|
+
const problems = validate(repo.root).errors;
|
|
21
|
+
if (problems.length) throw new Error(problems.map((p) => p.message).join("\n"));
|
|
22
|
+
return repo.changes(); // [{ path: "intent/storymap.yaml", text: "…" }, …]
|
|
23
|
+
});
|
|
24
|
+
// Commit `changes` as one commit, or open a pull or merge request with them.
|
|
25
|
+
// repo.deletions() lists the files to delete in the same commit (nikos migrate removes some).
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Inside `withStore`, every nikOS function reads and writes the store instead of
|
|
29
|
+
the disk. Outside it, they use the disk as the CLI does. `repo.root` is the
|
|
30
|
+
repo's root path inside the store (`/repo` by default); pass it, or
|
|
31
|
+
`<root>/intent`, wherever a function takes a folder.
|
|
32
|
+
|
|
33
|
+
## What needs a real checkout
|
|
34
|
+
|
|
35
|
+
Moving a story to done, `approve` and `verify` run the story's Cucumber
|
|
36
|
+
scenarios, so they need the code on disk. A tool without a checkout leaves
|
|
37
|
+
those to CI: `nikos ci setup` writes a pipeline that runs every story's
|
|
38
|
+
scenarios on each pull or merge request. The same goes for cloning upstream
|
|
39
|
+
plans (`nikos planning sync`) and the local dashboard.
|
|
40
|
+
|
|
41
|
+
## What's exported
|
|
42
|
+
|
|
43
|
+
| Area | Functions |
|
|
44
|
+
| --- | --- |
|
|
45
|
+
| Storage | `MemoryStore` (with `changes()` and `deletions()`), `withStore`, `diskStore`, `store` |
|
|
46
|
+
| Checks | `validate`, `formatResult`, `clear`, `checkRelease` |
|
|
47
|
+
| Story map | `init`, `newStory`, `newStep`, `addRelease`, `renameRelease`, `moveStory`, `removeRelease`, `reorderRelease`, `storyMapOf`, `renderMap`, `exportMap` |
|
|
48
|
+
| State | `loadStates`, `storyState`, `perStory`, `migrate` |
|
|
49
|
+
| Work | `setState`, `next`, `dependencies`, `suggest`, `show`, `newPlan`, `decide`, `findDecisions` |
|
|
50
|
+
| Approvals | `loadApprovals`, `approvalLapse` |
|
|
51
|
+
| Helpers | `STORY_ID`, `isDone`, `specHash`, `findIntentDir`, `featureFor` |
|
|
52
|
+
|
|
53
|
+
Types ship with the package.
|
package/docs/planning.md
CHANGED
|
@@ -12,6 +12,7 @@ planning/
|
|
|
12
12
|
bets/<bet-id>.md bet cards
|
|
13
13
|
upstream.yaml optional: plans in other repos this one works toward
|
|
14
14
|
upstream.lock written by `nikos planning sync`
|
|
15
|
+
downstream.yaml optional: departments and teams that plan against this one
|
|
15
16
|
views/<name>.html optional: custom dashboard views
|
|
16
17
|
```
|
|
17
18
|
|
|
@@ -105,17 +106,62 @@ Write one with `nikos planning bet new`; review it with
|
|
|
105
106
|
ever writing to them.
|
|
106
107
|
|
|
107
108
|
```yaml
|
|
108
|
-
- repo: acme/company-plan
|
|
109
|
+
- repo: acme/company-plan # on GitHub
|
|
109
110
|
ref: main
|
|
110
|
-
|
|
111
|
+
- repo: acme/product/payments-plan # on GitLab: the full path, with every group
|
|
112
|
+
host: gitlab.com # or your self-managed host, e.g. gitlab.acme.com
|
|
113
|
+
- repo: acme/ops-plan
|
|
114
|
+
url: ssh://git@git.example.com/acme/ops-plan.git # any other git host
|
|
111
115
|
```
|
|
112
116
|
|
|
113
|
-
`
|
|
117
|
+
`repo` is the repo's path on its host: `owner/repo` on GitHub, or
|
|
118
|
+
`group/subgroup/project` on GitLab. The clone url is
|
|
119
|
+
`https://<host>/<repo>.git`, with `host` defaulting to `github.com`; `url`
|
|
120
|
+
overrides it for anything else, including SSH.
|
|
121
|
+
|
|
122
|
+
`nikos planning sync` clones each into `.nikos/upstream/<repo>/`
|
|
114
123
|
(gitignored; sync adds it to `.gitignore`) and writes the commit it read to
|
|
115
|
-
`upstream.lock`.
|
|
124
|
+
`upstream.lock`. It follows the chain upwards: the plans a copy names in its
|
|
125
|
+
own `upstream.yaml` are synced too, so a team that names its department also
|
|
126
|
+
gets the company plan. `nikos join <repo>` adds an entry and syncs in one step;
|
|
127
|
+
settings inherited this way are in [settings.md](settings.md). A repo it can't reach is reported as unresolved and planning
|
|
116
128
|
carries on. `nikos planning sync --check` says whether an upstream has moved
|
|
117
129
|
past its locked commit. This repo only stores references like
|
|
118
|
-
`
|
|
130
|
+
`acme/company-plan#kr-2` or `acme/product/payments-plan#faster-checkout`, never
|
|
131
|
+
upstream content.
|
|
132
|
+
|
|
133
|
+
## downstream.yaml and the rollout
|
|
134
|
+
|
|
135
|
+
**Purpose:** the departments and teams that plan against this one, so a
|
|
136
|
+
company or department can see how the rollout is going.
|
|
137
|
+
|
|
138
|
+
```yaml
|
|
139
|
+
- name: Payments
|
|
140
|
+
repo: acme/payments-plan
|
|
141
|
+
- name: Growth # no repo yet
|
|
142
|
+
- name: Checkout
|
|
143
|
+
repo: acme/product/checkout
|
|
144
|
+
host: gitlab.com
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Entries take the same `repo`, `ref`, `host` and `url` as `upstream.yaml`.
|
|
148
|
+
`nikos company init --departments` writes the names; `nikos planning
|
|
149
|
+
downstream add "<name>" --repo <path>` adds one or gives it its repo.
|
|
150
|
+
|
|
151
|
+
`nikos planning rollout` clones each listed repo read-only into
|
|
152
|
+
`.nikos/downstream/<repo>/` (gitignored), follows the plans each one lists in
|
|
153
|
+
its own `downstream.yaml`, and reports for each:
|
|
154
|
+
|
|
155
|
+
- **joined**: whether its `upstream.yaml` names this plan (this repo's path comes
|
|
156
|
+
from its origin remote, or `--as <path>`)
|
|
157
|
+
- **synced**: when it last ran `nikos planning sync`, and whether it read this
|
|
158
|
+
repo's current commit
|
|
159
|
+
- **delivery**: stories done, approved and in progress, from its story map
|
|
160
|
+
- its key results and bets, and what each one points at
|
|
161
|
+
|
|
162
|
+
`--json` gives the same as data, and `--offline` reads the copies already
|
|
163
|
+
fetched. The dashboard's Rollout tab shows the last fetch and never fetches on
|
|
164
|
+
its own. A repo it can't reach is reported as unreachable and the rest carry on.
|
|
119
165
|
|
|
120
166
|
## views/<name>.html
|
|
121
167
|
|
package/docs/settings.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Settings, and how a company shares them
|
|
2
|
+
|
|
3
|
+
`nikos.settings.yaml`, at a repo's root, holds the choices that shape how
|
|
4
|
+
nikOS works there: whether stories need approval, and which tracker the team
|
|
5
|
+
uses. A company sets them once in its plan repo. Departments, groups and teams
|
|
6
|
+
that plan against it inherit them, and can change anything the levels above
|
|
7
|
+
haven't locked.
|
|
8
|
+
|
|
9
|
+
```yaml
|
|
10
|
+
format_version: 1
|
|
11
|
+
approval:
|
|
12
|
+
policy: required-for-release # off | optional (default) | required-for-release
|
|
13
|
+
routes: [app, chat] # the ways an approval counts: app, chat, terminal
|
|
14
|
+
tracker:
|
|
15
|
+
system: jira # none | jira | linear | github | gitlab | azure-devops | shortcut
|
|
16
|
+
site: acme.atlassian.net
|
|
17
|
+
project: PAY
|
|
18
|
+
statuses: # each tracker status, and what it means to nikOS
|
|
19
|
+
To Do: todo
|
|
20
|
+
In Progress: in_progress
|
|
21
|
+
Done: done
|
|
22
|
+
Accepted: approved
|
|
23
|
+
Won't Do: cancelled
|
|
24
|
+
approvals: false # true: a ticket moved to an "approved" status approves the story
|
|
25
|
+
locked: [approval.policy] # the levels below can't change these
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Every key is optional. See what applies, and where each value comes from:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
$ nikos settings
|
|
32
|
+
approval.policy: "required-for-release" (acme/company-plan; locked by acme/company-plan)
|
|
33
|
+
approval.routes: ["app","chat","terminal"] (default)
|
|
34
|
+
tracker.system: "jira" (acme/company-plan)
|
|
35
|
+
tracker.project: "PAY" (acme/engineering/payments-plan)
|
|
36
|
+
…
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Change one for this repo with `nikos settings set tracker.project PAY-WEB`.
|
|
40
|
+
It refuses if a level above locked it.
|
|
41
|
+
|
|
42
|
+
## Approval policy
|
|
43
|
+
|
|
44
|
+
- **off**: no approvals. The agent doesn't ask, and `nikos approve` refuses.
|
|
45
|
+
- **optional** (the default): when a story is done, the agent asks "Do you want
|
|
46
|
+
to approve …?". Approvals are recorded and shown, but never hold anything up.
|
|
47
|
+
- **required-for-release**: `nikos release check` counts a done story without a
|
|
48
|
+
sign-off as a gap. Day-to-day work isn't held up; shipping is.
|
|
49
|
+
|
|
50
|
+
`approval.routes` says which ways of approving count: through the app (`nikos
|
|
51
|
+
mcp` asks you directly), in chat (you answered your agent), or in a terminal.
|
|
52
|
+
To make approvals need the right reviewer, protect the main branch and add a
|
|
53
|
+
`CODEOWNERS` rule for `intent/approvals.yaml`. `nikos doctor` checks for it.
|
|
54
|
+
|
|
55
|
+
## Starting a company plan
|
|
56
|
+
|
|
57
|
+
`nikos company init --name "Acme" --period "Q1 2027" --departments "Payments, Growth"`
|
|
58
|
+
writes a company plan repo: `planning/plan.yaml`, the `nikos.settings.yaml`
|
|
59
|
+
every level inherits, `planning/downstream.yaml` listing the departments, and
|
|
60
|
+
a README with the line each department or team runs to join. Ask your agent to
|
|
61
|
+
"set nikOS up for our company" and the company playbook walks through it:
|
|
62
|
+
settings, the tracker, this period's objectives, creating the repo (only on
|
|
63
|
+
your yes), and the join line to share.
|
|
64
|
+
|
|
65
|
+
To see which departments and teams have joined, and how they're doing, run
|
|
66
|
+
`nikos planning rollout` in the company repo ([planning.md](planning.md)).
|
|
67
|
+
|
|
68
|
+
## Joining a company or department plan
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
nikos join acme/company-plan # on GitHub
|
|
72
|
+
nikos join acme/engineering/payments-plan --host gitlab.com # on GitLab
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`join` adds the repo to `planning/upstream.yaml`, fetches a read-only copy
|
|
76
|
+
(`nikos planning sync`), and shows the settings this repo now inherits.
|
|
77
|
+
Nothing in the repo moves, so a team that started on its own keeps everything
|
|
78
|
+
it has.
|
|
79
|
+
|
|
80
|
+
Sync follows the chain upwards. A team that joins its department also gets the
|
|
81
|
+
department's company plan, so company settings reach every level. Each copy is
|
|
82
|
+
read-only, in `.nikos/upstream/` (gitignored), and `planning/upstream.lock`
|
|
83
|
+
records the commit each one was read at.
|
|
84
|
+
|
|
85
|
+
Resolution goes from the top down: the defaults, then each level from the
|
|
86
|
+
company down, then this repo. A locked value stays as the level that locked it
|
|
87
|
+
set it.
|
|
88
|
+
|
|
89
|
+
## Checking a repo
|
|
90
|
+
|
|
91
|
+
`nikos doctor` checks the story map, the scenario runner, CI, approvals, the
|
|
92
|
+
tracker and the upstream plans. For each one it says what it found, and the
|
|
93
|
+
next thing to do when something's missing. Your agent runs it when something
|
|
94
|
+
seems off. `--offline` skips asking upstream repos whether they've moved on.
|
|
95
|
+
|
|
96
|
+
## Keeping a tracker in step
|
|
97
|
+
|
|
98
|
+
nikOS never calls Jira, Linear, GitHub or GitLab itself. Your agent reads and
|
|
99
|
+
writes tickets through the tracker's own MCP, which you connect and sign in to
|
|
100
|
+
in your app, and hands what it read to `nikos tracker`. The tracker playbook
|
|
101
|
+
walks through it: connect, map the statuses, import or link, keep in step.
|
|
102
|
+
|
|
103
|
+
| Command | What it does |
|
|
104
|
+
| --- | --- |
|
|
105
|
+
| `nikos tracker link <story> <ticket>` | Link a story to its ticket (`PAY-123`, `ENG-42`, `#57`), in `state.yaml` |
|
|
106
|
+
| `nikos tracker record <story> --status "<status>" --by "<who>"` | Record what a ticket says. A status mapped to `approved` approves the story when `tracker.approvals` is on (recorded as `via: tracker`, with who moved it); `cancelled` cancels it; anything else is only compared |
|
|
107
|
+
| `nikos tracker sync [--json]` | For each linked story, the status its ticket should be in. Your agent moves the tickets |
|
|
108
|
+
| `nikos tracker import <tickets.json>` | Bring a backlog in: `[{ "key", "title", "group" }]` become linked todo stories, each group an activity |
|
|
109
|
+
|
|
110
|
+
A ticket can never move a story to done. Done means the story's scenarios
|
|
111
|
+
pass, so when a ticket says Done and nikOS doesn't agree, nikOS keeps its own
|
|
112
|
+
status and lists the ticket to move back.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nikos",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
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",
|
|
@@ -13,6 +13,14 @@
|
|
|
13
13
|
"bin": {
|
|
14
14
|
"nikos": "dist/cli.js"
|
|
15
15
|
},
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./package.json": "./package.json"
|
|
22
|
+
},
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
16
24
|
"engines": {
|
|
17
25
|
"node": ">=20"
|
|
18
26
|
},
|
|
@@ -41,8 +49,10 @@
|
|
|
41
49
|
"dependencies": {
|
|
42
50
|
"@cucumber/gherkin": "^42.0.1",
|
|
43
51
|
"@cucumber/messages": "^34.2.1",
|
|
52
|
+
"@modelcontextprotocol/sdk": "^1.32.2",
|
|
44
53
|
"ajv": "^8.17.1",
|
|
45
|
-
"yaml": "^2.6.1"
|
|
54
|
+
"yaml": "^2.6.1",
|
|
55
|
+
"zod": "^3.25.76"
|
|
46
56
|
},
|
|
47
57
|
"keywords": [
|
|
48
58
|
"spec-driven-development",
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://nikos.tools/schemas/approvals.schema.json",
|
|
4
|
+
"title": "nikOS approvals",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["format_version", "approvals"],
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"format_version": { "const": 1 },
|
|
10
|
+
"approvals": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"propertyNames": { "pattern": "^[A-Z][A-Z0-9]{0,9}-[0-9]+$" },
|
|
13
|
+
"additionalProperties": {
|
|
14
|
+
"type": "object",
|
|
15
|
+
"required": ["by", "on", "spec_hash", "done_at"],
|
|
16
|
+
"additionalProperties": false,
|
|
17
|
+
"properties": {
|
|
18
|
+
"by": { "type": "string", "minLength": 1 },
|
|
19
|
+
"on": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" },
|
|
20
|
+
"via": { "enum": ["terminal", "chat", "app", "tracker"] },
|
|
21
|
+
"spec_hash": { "type": "string", "pattern": "^[0-9a-f]{12}$" },
|
|
22
|
+
"done_at": { "type": "string", "minLength": 1 }
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"target": { "type": "number" },
|
|
16
16
|
"original_target": { "type": "number" },
|
|
17
17
|
"by": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" },
|
|
18
|
-
"aligns_to": { "type": "string", "pattern": "^[A-Za-z0-9_.-]
|
|
18
|
+
"aligns_to": { "type": "string", "pattern": "^(?!\\.{1,2}(?:\\/|#|$))[A-Za-z0-9_.-]+(?:\\/(?!\\.{1,2}(?:\\/|#|$))[A-Za-z0-9_.-]+)+#[a-z0-9]+(-[a-z0-9]+)*$" },
|
|
19
19
|
"stale_after_days": { "type": "integer", "minimum": 1 },
|
|
20
20
|
"checkins": {
|
|
21
21
|
"type": "array",
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://nikos.tools/schemas/settings.schema.json",
|
|
4
|
+
"title": "nikOS settings",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["format_version"],
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"format_version": { "const": 1 },
|
|
10
|
+
"approval": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"additionalProperties": false,
|
|
13
|
+
"properties": {
|
|
14
|
+
"policy": { "enum": ["off", "optional", "required-for-release"] },
|
|
15
|
+
"routes": { "type": "array", "items": { "enum": ["app", "chat", "terminal"] }, "uniqueItems": true, "minItems": 1 }
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"tracker": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"additionalProperties": false,
|
|
21
|
+
"properties": {
|
|
22
|
+
"system": { "enum": ["none", "jira", "linear", "github", "gitlab", "azure-devops", "shortcut"] },
|
|
23
|
+
"site": { "type": "string" },
|
|
24
|
+
"project": { "type": "string" },
|
|
25
|
+
"statuses": {
|
|
26
|
+
"type": "object",
|
|
27
|
+
"description": "Each tracker status, and what it means to nikOS.",
|
|
28
|
+
"additionalProperties": { "enum": ["todo", "in_progress", "needs_rework", "done", "approved", "cancelled"] }
|
|
29
|
+
},
|
|
30
|
+
"approvals": { "type": "boolean", "description": "Whether moving a ticket to a status mapped to approved approves the story." }
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"locked": {
|
|
34
|
+
"type": "array",
|
|
35
|
+
"description": "Settings the levels below can't change.",
|
|
36
|
+
"items": { "enum": ["approval.policy", "approval.routes", "tracker.system", "tracker.site", "tracker.project", "tracker.statuses", "tracker.approvals"] },
|
|
37
|
+
"uniqueItems": true
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -15,11 +15,15 @@
|
|
|
15
15
|
"required": ["status"],
|
|
16
16
|
"additionalProperties": false,
|
|
17
17
|
"properties": {
|
|
18
|
-
"status": { "enum": ["todo", "in_progress", "needs_rework", "done"] },
|
|
18
|
+
"status": { "enum": ["todo", "in_progress", "needs_rework", "done", "cancelled"] },
|
|
19
19
|
"claimed_by": { "type": "string" },
|
|
20
20
|
"plan_version": { "type": "integer", "minimum": 1 },
|
|
21
21
|
"blockers": { "type": "array", "items": { "type": "string" } },
|
|
22
|
-
"updated": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" }
|
|
22
|
+
"updated": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" },
|
|
23
|
+
"spec_hash": { "type": "string", "pattern": "^[0-9a-f]{12}$" },
|
|
24
|
+
"done_at": { "type": "string", "minLength": 1 },
|
|
25
|
+
"cancelled_reason": { "type": "string", "minLength": 1 },
|
|
26
|
+
"ticket": { "type": "string", "minLength": 1 }
|
|
23
27
|
}
|
|
24
28
|
}
|
|
25
29
|
},
|
|
@@ -23,6 +23,19 @@
|
|
|
23
23
|
"type": "string",
|
|
24
24
|
"pattern": "^[A-Z][A-Z0-9]{0,9}$"
|
|
25
25
|
},
|
|
26
|
+
"personas": {
|
|
27
|
+
"type": "array",
|
|
28
|
+
"items": {
|
|
29
|
+
"type": "object",
|
|
30
|
+
"required": ["id", "title"],
|
|
31
|
+
"additionalProperties": false,
|
|
32
|
+
"properties": {
|
|
33
|
+
"id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]*$" },
|
|
34
|
+
"title": { "type": "string", "minLength": 1 },
|
|
35
|
+
"description": { "type": "string" }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
26
39
|
"activities": {
|
|
27
40
|
"type": "array",
|
|
28
41
|
"minItems": 1,
|