nikos 0.0.0-stage → 0.0.2

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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +107 -2
  3. package/dist/cli.js +60 -0
  4. package/dist/commands/bet.js +97 -0
  5. package/dist/commands/clear.js +100 -0
  6. package/dist/commands/decide.js +60 -0
  7. package/dist/commands/decisions.js +71 -0
  8. package/dist/commands/init.js +72 -0
  9. package/dist/commands/next.js +35 -0
  10. package/dist/commands/plan.js +64 -0
  11. package/dist/commands/show.js +82 -0
  12. package/dist/commands/state.js +104 -0
  13. package/dist/commands/validate.js +206 -0
  14. package/dist/commands/verify.js +99 -0
  15. package/dist/lib/project.js +42 -0
  16. package/dist/lint/constitution.js +40 -0
  17. package/dist/lint/gherkin.js +99 -0
  18. package/dist/lint/portable.js +66 -0
  19. package/dist/verify/detect.js +21 -0
  20. package/docs/format.md +296 -0
  21. package/docs/gherkin.md +81 -0
  22. package/docs/install.md +72 -0
  23. package/docs/intentdocs-mapping.md +117 -0
  24. package/docs/quickstart.md +153 -0
  25. package/package.json +44 -4
  26. package/schemas/bet.schema.json +23 -0
  27. package/schemas/state.schema.json +34 -0
  28. package/schemas/storymap.schema.json +43 -0
  29. package/skills/clear/SKILL.md +55 -0
  30. package/skills/clear-review/SKILL.md +45 -0
  31. package/skills/decisions/SKILL.md +45 -0
  32. package/skills/intent/SKILL.md +39 -0
  33. package/skills/nikos-mode/SKILL.md +61 -0
  34. package/skills/nikos-mode/playbooks/answer.md +9 -0
  35. package/skills/nikos-mode/playbooks/bet.md +14 -0
  36. package/skills/nikos-mode/playbooks/build-a-story.md +14 -0
  37. package/skills/nikos-mode/playbooks/change.md +12 -0
  38. package/skills/nikos-mode/playbooks/idea-to-blueprint.md +16 -0
  39. package/skills/nikos-mode/playbooks/index.md +13 -0
  40. package/skills/nikos-mode/playbooks/validate.md +17 -0
  41. package/skills/nikos-mode/principles/index.md +14 -0
  42. package/skills/verify/SKILL.md +49 -0
  43. package/templates/intent/constitution.md +19 -0
  44. package/templates/intent/decisions.md +12 -0
  45. package/templates/intent/features/getting-started/first-story.feature +12 -0
  46. package/templates/intent/plans/.gitkeep +0 -0
  47. package/templates/intent/state.yaml +4 -0
  48. package/templates/intent/storymap.yaml +10 -0
  49. package/templates/plan.md +13 -0
  50. package/templates/verify/js/cucumber.mjs +6 -0
  51. package/templates/verify/js/test/steps/first-story.steps.mjs +16 -0
  52. package/templates/verify/python/behave.ini +4 -0
  53. package/templates/verify/python/steps/first_story_steps.py +19 -0
  54. package/templates/verify/ts/cucumber.mjs +6 -0
  55. package/templates/verify/ts/test/steps/first-story.steps.ts +16 -0
@@ -0,0 +1,99 @@
1
+ import { AstBuilder, GherkinClassicTokenMatcher, Parser } from "@cucumber/gherkin";
2
+ import { IdGenerator } from "@cucumber/messages";
3
+ import { STORY_ID } from "../lib/project.js";
4
+ /** Tags that describe progress, which belongs in state.yaml, not the spec. */
5
+ const STATUS_TAGS = /^@(todo|done|wip|in[-_]?progress|doing|blocked|needs[-_]?rework|ready|review|approved|complete[d]?|skip|pending)$/i;
6
+ const PRIORITY = /^@(must|should|could)$/;
7
+ const AC_TAG = /^@AC\d+$/;
8
+ const KNOWN_FEATURE_TAG = [STORY_ID, /^release-\d+$/, /^(must|should|could)$/, /^activity-[a-z0-9-]+$/, /^effort-(XS|S|M|L|XL)$/];
9
+ function parser() {
10
+ return new Parser(new AstBuilder(IdGenerator.uuid()), new GherkinClassicTokenMatcher());
11
+ }
12
+ /** Parse a feature file. Throws on invalid Gherkin. */
13
+ export function parseFeature(text) {
14
+ return parser().parse(text).feature;
15
+ }
16
+ /** Every scenario in a feature, including those inside Rules. */
17
+ export function scenariosOf(feature) {
18
+ return feature.children.flatMap((c) => (c.scenario ? [c.scenario] : c.rule ? c.rule.children.flatMap((rc) => (rc.scenario ? [rc.scenario] : [])) : []));
19
+ }
20
+ export function lintFeature(text) {
21
+ const issues = [];
22
+ const featureLines = text.split("\n").filter((l) => /^\s*Feature:/.test(l)).length;
23
+ if (featureLines > 1) {
24
+ issues.push({ level: "error", message: `has ${featureLines} Features`, hint: "one Feature per file, one file per story" });
25
+ }
26
+ let doc;
27
+ try {
28
+ doc = parser().parse(text);
29
+ }
30
+ catch (err) {
31
+ issues.push({ level: "error", message: `isn't valid Gherkin: ${err.message.split("\n")[0]}` });
32
+ return issues;
33
+ }
34
+ const feature = doc.feature;
35
+ if (!feature) {
36
+ issues.push({ level: "error", message: "has no Feature", hint: "start the file with a story id tag and `Feature: <title>`" });
37
+ return issues;
38
+ }
39
+ const tags = feature.tags.map((t) => t.name);
40
+ for (const tag of tags) {
41
+ if (STATUS_TAGS.test(tag)) {
42
+ issues.push({ level: "error", message: `uses status tag ${tag}`, hint: "status lives in state.yaml, not in the feature" });
43
+ }
44
+ else if (!KNOWN_FEATURE_TAG.some((re) => re.test(tag.slice(1)))) {
45
+ issues.push({ level: "warning", message: `has unrecognised tag ${tag}`, hint: "allowed: story id, @release-N, @must/@should/@could, @activity-<id>, @effort-<size>" });
46
+ }
47
+ }
48
+ if (tags.filter((t) => PRIORITY.test(t)).length !== 1) {
49
+ issues.push({ level: "error", message: "needs exactly one priority tag on the Feature", hint: "add @must, @should or @could" });
50
+ }
51
+ if (!tags.some((t) => /^@release-\d+$/.test(t))) {
52
+ issues.push({ level: "error", message: "has no @release-N tag" });
53
+ }
54
+ const desc = feature.description ?? "";
55
+ // "As a", "As an" and "As the" all name a role; I want and So that are fixed.
56
+ const parts = [["As a", /^\s*As (?:an?|the)\s+\S/im], ["I want", /^\s*I want\b/im], ["So that", /^\s*So that\b/im]];
57
+ const missing = parts.filter(([, re]) => !re.test(desc)).map(([label]) => label);
58
+ if (missing.length) {
59
+ issues.push({
60
+ level: "error",
61
+ message: `user story is missing ${missing.map((m) => `"${m}"`).join(", ")}`,
62
+ hint: "put As a … / I want … / So that … on separate lines under Feature:",
63
+ });
64
+ }
65
+ const scenarios = scenariosOf(feature);
66
+ if (scenarios.length === 0) {
67
+ issues.push({ level: "warning", message: "has no scenarios yet", hint: "add one Scenario per acceptance criterion" });
68
+ }
69
+ const labels = new Set();
70
+ for (const s of scenarios) {
71
+ const sTags = s.tags.map((t) => t.name);
72
+ const ac = sTags.find((t) => AC_TAG.test(t));
73
+ for (const tag of sTags) {
74
+ if (STATUS_TAGS.test(tag)) {
75
+ issues.push({ level: "error", message: `scenario "${s.name}" uses status tag ${tag}`, hint: "status lives in state.yaml, not in the feature" });
76
+ }
77
+ }
78
+ if (!ac) {
79
+ issues.push({ level: "error", message: `scenario "${s.name}" has no @AC<n> tag`, hint: "tag each scenario with its criterion label, e.g. @AC1" });
80
+ continue;
81
+ }
82
+ if (labels.has(ac))
83
+ issues.push({ level: "error", message: `${ac} is used by more than one scenario` });
84
+ labels.add(ac);
85
+ if (!s.name.startsWith(`${ac.slice(1)} `)) {
86
+ issues.push({ level: "warning", message: `scenario "${s.name}" should start with its label`, hint: `e.g. "${ac.slice(1)} — ${s.name}"` });
87
+ }
88
+ if (sTags.filter((t) => PRIORITY.test(t)).length > 1) {
89
+ issues.push({ level: "error", message: `scenario "${s.name}" has more than one priority tag` });
90
+ }
91
+ // Given is optional (a scenario can start from nothing); When and Then are not.
92
+ const keywords = new Set(s.steps.map((st) => st.keyword.trim()));
93
+ for (const k of ["When", "Then"]) {
94
+ if (!keywords.has(k))
95
+ issues.push({ level: "error", message: `scenario "${s.name}" has no ${k} step` });
96
+ }
97
+ }
98
+ return issues;
99
+ }
@@ -0,0 +1,66 @@
1
+ import { readdirSync, readFileSync, statSync } from "node:fs";
2
+ import { join, relative } from "node:path";
3
+ import { parse } from "yaml";
4
+ /** The only frontmatter keys every harness (Claude Code, Cursor, Codex) understands. */
5
+ const ALLOWED_KEYS = ["name", "description"];
6
+ /** Tool names that exist in one harness only. A portable skill describes the action instead. */
7
+ const HARNESS_TOOLS = [
8
+ "TodoWrite", "TodoRead", "TaskCreate", "TaskUpdate", "TaskList", "AskUserQuestion",
9
+ "EnterPlanMode", "ExitPlanMode", "NotebookEdit", "SlashCommand", "todo_write", "update_plan",
10
+ "apply_patch", "$ARGUMENTS", "allowed-tools",
11
+ ];
12
+ /** Lint one SKILL.md (or a file it links to) for harness-specific features. */
13
+ export function lintSkill(text, { isEntry = true } = {}) {
14
+ const issues = [];
15
+ let body = text;
16
+ if (isEntry) {
17
+ const m = /^---\n([\s\S]*?)\n---\n/.exec(text);
18
+ if (!m) {
19
+ issues.push({ level: "error", message: "has no frontmatter", hint: "start with ---, name: and description:, then ---" });
20
+ }
21
+ else {
22
+ body = text.slice(m[0].length);
23
+ let meta = {};
24
+ try {
25
+ meta = parse(m[1]) ?? {};
26
+ }
27
+ catch (err) {
28
+ issues.push({ level: "error", message: `frontmatter isn't valid YAML: ${err.message.split("\n")[0]}` });
29
+ }
30
+ for (const key of Object.keys(meta)) {
31
+ if (!ALLOWED_KEYS.includes(key)) {
32
+ issues.push({ level: "error", message: `frontmatter key '${key}' isn't portable`, hint: "use only name and description" });
33
+ }
34
+ }
35
+ for (const key of ALLOWED_KEYS) {
36
+ if (typeof meta[key] !== "string" || !meta[key].trim()) {
37
+ issues.push({ level: "error", message: `frontmatter is missing '${key}'` });
38
+ }
39
+ }
40
+ }
41
+ }
42
+ for (const tool of HARNESS_TOOLS) {
43
+ if (body.includes(tool)) {
44
+ issues.push({ level: "error", message: `names the harness-only tool '${tool}'`, hint: "describe the action (\"your task list\") instead" });
45
+ }
46
+ }
47
+ return issues;
48
+ }
49
+ /** Lint every Markdown file under a skills/ folder. SKILL.md files are entry points and need frontmatter. */
50
+ export function lintSkills(dir) {
51
+ const issues = [];
52
+ const walk = (d) => {
53
+ for (const name of readdirSync(d)) {
54
+ const path = join(d, name);
55
+ if (statSync(path).isDirectory())
56
+ walk(path);
57
+ else if (name.endsWith(".md")) {
58
+ const file = relative(dir, path);
59
+ for (const i of lintSkill(readFileSync(path, "utf8"), { isEntry: name === "SKILL.md" }))
60
+ issues.push({ ...i, file });
61
+ }
62
+ }
63
+ };
64
+ walk(dir);
65
+ return issues;
66
+ }
@@ -0,0 +1,21 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ const PYTHON_MARKERS = ["pyproject.toml", "requirements.txt", "setup.py", "setup.cfg", "Pipfile"];
4
+ /** Pick the Cucumber runner's stack from the files at the repo root. Undefined means ask the person. */
5
+ export function detectStack(root) {
6
+ const pkgPath = join(root, "package.json");
7
+ if (existsSync(pkgPath)) {
8
+ let pkg = {};
9
+ try {
10
+ pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
11
+ }
12
+ catch {
13
+ // An unreadable package.json is still a JS project.
14
+ }
15
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
16
+ return existsSync(join(root, "tsconfig.json")) || "typescript" in deps ? "ts" : "js";
17
+ }
18
+ if (PYTHON_MARKERS.some((f) => existsSync(join(root, f))))
19
+ return "python";
20
+ return undefined;
21
+ }
package/docs/format.md ADDED
@@ -0,0 +1,296 @@
1
+ # The intent/ format
2
+
3
+ Format version: **1**
4
+
5
+ nikOS keeps a project's intent — what it's for, what to build, how it's going —
6
+ in an `intent/` folder at the root of the repo. Everything is plain text, so
7
+ agents, humans, git and code review all see the same thing.
8
+
9
+ ```
10
+ intent/
11
+ constitution.md the project's vision
12
+ storymap.yaml activities, releases and story order
13
+ features/
14
+ <activity>/
15
+ <story-slug>.feature one story, its criteria as scenarios
16
+ plans/
17
+ <story-id>.v<N>.md versioned implementation plans
18
+ decisions.md append-only decision log
19
+ state.yaml status, blockers and the run queue
20
+ outreach/ optional, drafts from the validate playbook
21
+ <date>-<channel>.md
22
+ bets/ optional, time-boxed bets (see Bets)
23
+ <bet-id>.md
24
+ ```
25
+
26
+ Two rules hold across every file:
27
+
28
+ 1. **Specs describe intent, not implementation.** Feature files and the
29
+ constitution say what and why. File paths, frameworks and code belong in
30
+ plans and decisions.
31
+ 2. **Changing state never edits a spec.** Status, owners, blockers and dates
32
+ live in `state.yaml`, so a spec's diff only changes when the intent does.
33
+
34
+ Every file below is listed with its **purpose**, **path** and **format**.
35
+
36
+ ---
37
+
38
+ ## constitution.md
39
+
40
+ **Purpose:** the project's vision, answered once and refined as you learn.
41
+ Every story should trace back to it.
42
+
43
+ **Path:** `intent/constitution.md`
44
+
45
+ **Format:** Markdown with YAML front matter and exactly these five `##`
46
+ sections, in this order:
47
+
48
+ ```markdown
49
+ ---
50
+ name: Hello Todo
51
+ idea: A todo list that only shows today's three most important tasks.
52
+ ---
53
+
54
+ ## Users
55
+ Who it's for.
56
+
57
+ ## Problem
58
+ What hurts today, in their words.
59
+
60
+ ## Switch
61
+ What makes them switch now, and from what.
62
+
63
+ ## First
64
+ The smallest first version that proves it.
65
+
66
+ ## Proof
67
+ How you'll know it's working.
68
+ ```
69
+
70
+ Further `##` sections may follow the five required ones. The
71
+ idea-to-blueprint playbook writes `## Jobs to be done` and
72
+ `## Founding hypothesis`; the validate playbook writes `## Validation`, one entry per round of
73
+ outreach, and keeps its drafts in `intent/outreach/`. A draft is marked
74
+ `Status: draft, not sent` until the founder signs it off; the founder sends it.
75
+ `nikos validate` checks the front matter has `name` and `idea`, and that the
76
+ five vision sections come first, in order, and aren't empty.
77
+
78
+ ---
79
+
80
+ ## storymap.yaml
81
+
82
+ **Purpose:** the shape of the product: activities (columns), releases (rows)
83
+ and the order stories should be built in. It is an index; the stories
84
+ themselves live in feature files.
85
+
86
+ **Path:** `intent/storymap.yaml`
87
+
88
+ **Format:** YAML.
89
+
90
+ ```yaml
91
+ format_version: 1
92
+ project: hello-todo
93
+ key: HT # story id prefix: HT-1, HT-2 …
94
+ activities:
95
+ - id: tasks # slug, matches the features/<activity>/ folder
96
+ title: Manage tasks
97
+ releases:
98
+ - number: 1
99
+ title: MVP
100
+ stories: [HT-1, HT-2] # build order within the release
101
+ - number: 2
102
+ title: Enhancements
103
+ stories: [HT-3]
104
+ ```
105
+
106
+ Rules:
107
+
108
+ - Every story id appears in exactly one release.
109
+ - Every story id has exactly one feature file and one `state.yaml` entry.
110
+ - Release numbers start at 1 and have no gaps.
111
+
112
+ ---
113
+
114
+ ## features/&lt;activity&gt;/&lt;story-slug&gt;.feature
115
+
116
+ **Purpose:** one user story and its acceptance criteria, written so they
117
+ run as tests.
118
+
119
+ **Path:** `intent/features/<activity-id>/<story-slug>.feature`, where the
120
+ activity id matches `storymap.yaml` and the slug is the story title in
121
+ kebab-case.
122
+
123
+ **Format:** Gherkin. One `Feature` per file. The description holds the user
124
+ story; each acceptance criterion is a `Scenario` (or `Scenario Outline`)
125
+ titled with its label. Full rules are in [gherkin.md](gherkin.md).
126
+
127
+ ```gherkin
128
+ @HT-1 @release-1 @must @activity-tasks
129
+ Feature: Add a task
130
+
131
+ As a busy person
132
+ I want to add a task in one step
133
+ So that capturing it is faster than forgetting it
134
+
135
+ @AC1 @must
136
+ Scenario: AC1 — Adding a task shows it at the top
137
+ Given my list is empty
138
+ When I add the task "Call the bank"
139
+ Then I see "Call the bank" at the top of my list
140
+ ```
141
+
142
+ ---
143
+
144
+ ## plans/&lt;story-id&gt;.v&lt;N&gt;.md
145
+
146
+ **Purpose:** how a story will be built. Plans are versioned: a new approach
147
+ is a new file, so history is never lost.
148
+
149
+ **Path:** `intent/plans/<story-id>.v<N>.md`, for example
150
+ `intent/plans/HT-1.v2.md`. `N` starts at 1 and increases by one.
151
+
152
+ **Format:** Markdown with front matter, then one `##` section per step:
153
+
154
+ ```markdown
155
+ ---
156
+ story: HT-1
157
+ version: 2
158
+ created: 2026-11-03
159
+ supersedes: 1 # omit for v1
160
+ summary: One to three plain sentences on the approach.
161
+ ---
162
+
163
+ ## 1. Store tasks locally
164
+ **Why:** tasks survive a refresh.
165
+ **Criteria:** AC1
166
+ **Files:** src/store.ts
167
+
168
+ Notes for the agent.
169
+ ```
170
+
171
+ These fields match intentdocs plans one to one (see the mapping doc).
172
+
173
+ Start a new version with `nikos plan new <story-id>`. It writes the next
174
+ `v<N>` from a template, sets `supersedes`, and refuses to overwrite an
175
+ existing file.
176
+
177
+ ---
178
+
179
+ ## decisions.md
180
+
181
+ **Purpose:** why things are the way they are. Append-only; never rewrite an
182
+ entry, add a new one that supersedes it.
183
+
184
+ **Path:** `intent/decisions.md`
185
+
186
+ **Format:** Markdown. One `##` entry per decision, newest last:
187
+
188
+ ```markdown
189
+ ## 2026-11-03 — Store tasks in localStorage (HT-1)
190
+ **Category:** architecture
191
+ **Supersedes:** none
192
+
193
+ Why this, and what we rejected.
194
+ ```
195
+
196
+ The heading is `date — title (story id)`; the story id is optional for
197
+ project-wide decisions. Category is one of `product`, `architecture`,
198
+ `design`, `process`.
199
+
200
+ ---
201
+
202
+ ## state.yaml
203
+
204
+ **Purpose:** everything that changes as work happens: status, who's on it,
205
+ blockers, and the run queue. Kept apart so specs stay clean.
206
+
207
+ **Path:** `intent/state.yaml`
208
+
209
+ **Format:** YAML.
210
+
211
+ ```yaml
212
+ format_version: 1
213
+ stories:
214
+ HT-1:
215
+ status: done # todo | in_progress | needs_rework | done
216
+ claimed_by: claude-code # optional
217
+ plan_version: 2 # optional, the plan in use
218
+ blockers: [] # optional, plain sentences
219
+ updated: 2026-11-03
220
+ HT-2:
221
+ status: todo
222
+ run: # optional, an ordered build queue
223
+ queue: [HT-2]
224
+ ```
225
+
226
+ Status moves `todo → in_progress → done`, or `in_progress → needs_rework →
227
+ in_progress`. Only a passing verification can set `done` (see the
228
+ build-a-story playbook).
229
+
230
+ ---
231
+
232
+ ## outreach/&lt;date&gt;-&lt;channel&gt;.md (optional)
233
+
234
+ **Purpose:** outreach drafted to test the idea with real people, kept so the
235
+ founder can review it and the results can be traced back to it.
236
+
237
+ **Path:** `intent/outreach/<yyyy-mm-dd>-<channel>.md`
238
+
239
+ **Format:** Markdown. The first line is the status: `Status: draft, not sent`
240
+ until the founder signs it off, then `Status: signed off <date>, for the
241
+ person to send`. Then who it's for, the message, the questions to ask and,
242
+ optionally, a demo or reel script. Written by the validate playbook; the
243
+ founder sends it, never the agent.
244
+
245
+ ---
246
+
247
+ ## bets/&lt;bet-id&gt;.md (optional)
248
+
249
+ **Purpose:** a time-boxed bet that a set of stories will move a measure.
250
+
251
+ **Path:** `intent/bets/<bet-id>.md`, where `<bet-id>` is lowercase words
252
+ joined by hyphens.
253
+
254
+ **Format:** Markdown with YAML front matter, then free-form notes. Create one
255
+ with `nikos bet new`; the bet playbook frames and closes it.
256
+
257
+ ```markdown
258
+ ---
259
+ id: faster-reorder
260
+ hypothesis: We believe drag to reorder will make people plan their day in the app because they reorder on paper today.
261
+ key_result: Share of daily users who reorder at least once
262
+ expected_uplift: from 10% to 30%
263
+ time_box: 2026-12-01
264
+ blockers: []
265
+ stories: [HT-3]
266
+ status: open
267
+ ---
268
+
269
+ ## Why we think so
270
+ The evidence behind the hypothesis.
271
+
272
+ ## Outcome
273
+ Filled in when the time box ends.
274
+ ```
275
+
276
+ | Field | Required | Meaning |
277
+ | --- | --- | --- |
278
+ | `id` | yes | Matches the file name |
279
+ | `hypothesis` | yes | What you believe and why |
280
+ | `key_result` | yes | The one measure that says whether it worked |
281
+ | `expected_uplift` | yes | How far you expect it to move, from what to what |
282
+ | `time_box` | yes | End date, `YYYY-MM-DD` |
283
+ | `blockers` | yes | What stands in the way; `[]` if nothing |
284
+ | `stories` | yes | At least one story id; each must be in `storymap.yaml` |
285
+ | `status` | yes | `open \| won \| lost \| killed` |
286
+
287
+ `nikos validate` checks every field against `schemas/bet.schema.json`, that
288
+ the id matches the file name, and that every linked story exists.
289
+
290
+ ---
291
+
292
+ ## Versioning this format
293
+
294
+ `format_version` in `storymap.yaml` and `state.yaml` is the version of this
295
+ document. Breaking changes bump it; `nikos validate` reads it and explains
296
+ any migration needed.
@@ -0,0 +1,81 @@
1
+ # Writing stories as feature files
2
+
3
+ Every story is one Gherkin `.feature` file in
4
+ `intent/features/<activity>/<story-slug>.feature`. The file is both the spec
5
+ and the test: each acceptance criterion is a scenario that Cucumber can run.
6
+ `nikos validate` checks all the rules on this page.
7
+
8
+ ## The shape of a story
9
+
10
+ ```gherkin
11
+ @HT-1 @release-1 @must @activity-tasks @effort-S
12
+ Feature: Add a task
13
+
14
+ As a busy person
15
+ I want to add a task in one step
16
+ So that capturing it is faster than forgetting it
17
+
18
+ @AC1 @must
19
+ Scenario: AC1 — Adding a task shows it at the top
20
+ Given my list is empty
21
+ When I add the task "Call the bank"
22
+ Then I see "Call the bank" at the top of my list
23
+
24
+ @AC2 @should
25
+ Scenario: AC2 — Blank tasks are ignored
26
+ Given my list is empty
27
+ When I add the task ""
28
+ Then my list is still empty
29
+ ```
30
+
31
+ ## Rules
32
+
33
+ **One story per file.** Exactly one `Feature`, and exactly one story id tag.
34
+
35
+ **The user story goes in the Feature description.** Three lines under
36
+ `Feature:`, starting `As a`, `I want` and `So that`.
37
+
38
+ **Each acceptance criterion is a scenario.** Tag it `@AC1`, `@AC2` … and
39
+ start its title with the same label (`AC1 — …`). Labels are unique within
40
+ the file. A `Scenario Outline` counts as one criterion. Scenarios may sit
41
+ inside a `Rule:` block.
42
+
43
+ **Every scenario has a When and a Then.** `Given` is optional.
44
+
45
+ **Priority is a tag.** Exactly one of `@must`, `@should` or `@could` on the
46
+ Feature (the story's priority), and optionally one on each scenario (the
47
+ criterion's priority).
48
+
49
+ ## Allowed Feature tags
50
+
51
+ | Tag | Meaning | Required |
52
+ |---|---|---|
53
+ | `@HT-1` | story id, using the project key | yes |
54
+ | `@release-N` | release the story is in | yes |
55
+ | `@must` / `@should` / `@could` | story priority | yes |
56
+ | `@activity-<id>` | activity, matching the folder name | no |
57
+ | `@effort-XS` … `@effort-XL` | size estimate | no |
58
+
59
+ Anything else is a warning, so you notice typos.
60
+
61
+ ## Never put status in a feature
62
+
63
+ Tags like `@todo`, `@wip`, `@in-progress`, `@done`, `@blocked` or `@ready`
64
+ are errors. Status, owners, blockers and dates live in `intent/state.yaml`,
65
+ so a feature file only changes when the intent changes and its git history
66
+ reads as a history of decisions about the product.
67
+
68
+ ## Keep it about behaviour, not implementation
69
+
70
+ Write steps the way a user would describe what happens: "When I add the task
71
+ 'Call the bank'", not "When I POST to /api/tasks". Implementation detail
72
+ belongs in the story's plan. `nikos clear` flags file paths, endpoints and
73
+ framework names in feature text.
74
+
75
+ ## Checking your work
76
+
77
+ ```sh
78
+ npx nikos validate
79
+ ```
80
+
81
+ Errors block (exit code 1); warnings are shown but don't fail.
@@ -0,0 +1,72 @@
1
+ # Installing nikOS in your agent tool
2
+
3
+ nikOS is one set of skills in `skills/`. Each tool reads its own small
4
+ manifest, and every manifest points at that same folder, so no skill file is
5
+ copied per tool.
6
+
7
+ | Tool | Manifest | Install |
8
+ | --- | --- | --- |
9
+ | Claude Code | `.claude-plugin/plugin.json`, `.claude-plugin/marketplace.json` | `/plugin install nikos --marketplace nikovijay/NikOS` |
10
+ | Codex | `.codex-plugin/plugin.json`, `.agents/plugins/marketplace.json` | `codex plugin marketplace add nikovijay/NikOS` |
11
+ | Cursor | `.cursor-plugin/plugin.json` | Add the repo as a marketplace (below) |
12
+
13
+ After installing, ask the agent to plan, build or check a story. The
14
+ `nikos-mode` skill picks the playbook.
15
+
16
+ ## Claude Code
17
+
18
+ In a session:
19
+
20
+ ```
21
+ /plugin install nikos --marketplace nikovijay/NikOS
22
+ ```
23
+
24
+ That works in one step on Claude Code 2.1.275 or later. On older versions,
25
+ add the marketplace first, then install:
26
+
27
+ ```
28
+ /plugin marketplace add nikovijay/NikOS
29
+ /plugin install nikos@nikos
30
+ ```
31
+
32
+ From a terminal, the same commands are `claude plugin marketplace add
33
+ nikovijay/NikOS` and `claude plugin install nikos@nikos`.
34
+
35
+ ## Codex
36
+
37
+ ```
38
+ codex plugin marketplace add nikovijay/NikOS
39
+ ```
40
+
41
+ Then install nikOS from the Plugins directory. Update it later with `codex
42
+ plugin marketplace upgrade`.
43
+
44
+ ## Cursor
45
+
46
+ Cursor installs plugins from a marketplace in its settings rather than a
47
+ command. Add `https://github.com/nikovijay/NikOS` as a marketplace (Plugins,
48
+ then Add Marketplace, then Import from Repo), and install nikOS from it.
49
+
50
+ To try it without a marketplace, copy the repo to
51
+ `~/.cursor/plugins/local/nikos` and run "Developer: Reload Window".
52
+
53
+ ## The command-line tool
54
+
55
+ The skills run the `nikos` command. When it isn't installed, they run it
56
+ through `npx -y nikos`, so nothing has to be installed globally. To install
57
+ it anyway: `npm install -g nikos`.
58
+
59
+ ## Formats checked
60
+
61
+ Plugin formats change often. These were read on 2026-11-04 (no page showed a
62
+ version):
63
+
64
+ - Claude Code: https://code.claude.com/docs/en/plugins-reference and
65
+ https://code.claude.com/docs/en/plugins/marketplace-reference. Checked with
66
+ `claude plugin validate .` on Claude Code 2.1.318.
67
+ - Cursor: https://cursor.com/docs/reference/plugins
68
+ - Codex: https://developers.openai.com/plugins/build/plugins
69
+
70
+ All three find skills as `skills/<name>/SKILL.md` at the plugin root.
71
+ `test/install.test.ts` checks the manifests agree with each other and with
72
+ the skills folder. `evals/install.md` records each install tried.