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.
- package/LICENSE +21 -0
- package/README.md +107 -2
- package/dist/cli.js +60 -0
- package/dist/commands/bet.js +97 -0
- package/dist/commands/clear.js +100 -0
- package/dist/commands/decide.js +60 -0
- package/dist/commands/decisions.js +71 -0
- package/dist/commands/init.js +72 -0
- package/dist/commands/next.js +35 -0
- package/dist/commands/plan.js +64 -0
- package/dist/commands/show.js +82 -0
- package/dist/commands/state.js +104 -0
- package/dist/commands/validate.js +206 -0
- package/dist/commands/verify.js +99 -0
- package/dist/lib/project.js +42 -0
- package/dist/lint/constitution.js +40 -0
- package/dist/lint/gherkin.js +99 -0
- package/dist/lint/portable.js +66 -0
- package/dist/verify/detect.js +21 -0
- package/docs/format.md +296 -0
- package/docs/gherkin.md +81 -0
- package/docs/install.md +72 -0
- package/docs/intentdocs-mapping.md +117 -0
- package/docs/quickstart.md +153 -0
- package/package.json +44 -4
- package/schemas/bet.schema.json +23 -0
- package/schemas/state.schema.json +34 -0
- package/schemas/storymap.schema.json +43 -0
- package/skills/clear/SKILL.md +55 -0
- package/skills/clear-review/SKILL.md +45 -0
- package/skills/decisions/SKILL.md +45 -0
- package/skills/intent/SKILL.md +39 -0
- package/skills/nikos-mode/SKILL.md +61 -0
- package/skills/nikos-mode/playbooks/answer.md +9 -0
- package/skills/nikos-mode/playbooks/bet.md +14 -0
- package/skills/nikos-mode/playbooks/build-a-story.md +14 -0
- package/skills/nikos-mode/playbooks/change.md +12 -0
- package/skills/nikos-mode/playbooks/idea-to-blueprint.md +16 -0
- package/skills/nikos-mode/playbooks/index.md +13 -0
- package/skills/nikos-mode/playbooks/validate.md +17 -0
- package/skills/nikos-mode/principles/index.md +14 -0
- package/skills/verify/SKILL.md +49 -0
- package/templates/intent/constitution.md +19 -0
- package/templates/intent/decisions.md +12 -0
- package/templates/intent/features/getting-started/first-story.feature +12 -0
- package/templates/intent/plans/.gitkeep +0 -0
- package/templates/intent/state.yaml +4 -0
- package/templates/intent/storymap.yaml +10 -0
- package/templates/plan.md +13 -0
- package/templates/verify/js/cucumber.mjs +6 -0
- package/templates/verify/js/test/steps/first-story.steps.mjs +16 -0
- package/templates/verify/python/behave.ini +4 -0
- package/templates/verify/python/steps/first_story_steps.py +19 -0
- package/templates/verify/ts/cucumber.mjs +6 -0
- 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/<activity>/<story-slug>.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/<story-id>.v<N>.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/<date>-<channel>.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/<bet-id>.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.
|
package/docs/gherkin.md
ADDED
|
@@ -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.
|
package/docs/install.md
ADDED
|
@@ -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.
|