nikos 0.2.0 → 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 CHANGED
@@ -48,6 +48,7 @@ install. More in [docs/install.md](docs/install.md).
48
48
 
49
49
  You don't need to learn the commands. Your agent runs them and, after each
50
50
  request, says where things stand and offers a short menu of what to do next.
51
+ In Claude Code, Codex and Cursor, it also does this when a session starts.
51
52
  Things you can say:
52
53
 
53
54
  - "Set up nikOS here", or "Map what this repo already does"
@@ -113,7 +114,7 @@ shell can use nikOS as a library: [docs/library.md](docs/library.md). A worked e
113
114
  | `nikos clear <id>` | Check a story is ready to build |
114
115
  | `nikos plan new <id>` | Start the next version of a story's plan |
115
116
  | `nikos verify setup` | Add a Cucumber runner for this repo's stack |
116
- | `nikos ci setup` | Run validate and every story's scenarios on each PR (GitHub Actions) or merge request (GitLab CI) |
117
+ | `nikos ci setup` | Run validate and every story's scenarios on each PR (GitHub Actions) or merge request (GitLab CI), plus a release check you run by hand before shipping |
117
118
  | `nikos verify [id]` | Run a story's scenarios, or all of them |
118
119
  | `nikos state set <id> <status>` | Move a story; done only if its scenarios pass; cancelled needs a reason |
119
120
  | `nikos approve <id>...` | Sign off done stories, in your terminal or (`--via chat`) by answering your agent (writes `intent/approvals.yaml`) |
@@ -2,6 +2,7 @@ import { type Stack } from "../verify/detect.js";
2
2
  export type Host = "github" | "gitlab";
3
3
  /** github or gitlab, from the origin remote's url; undefined when it's neither or there's no remote. */
4
4
  export declare function detectHost(root: string): Host | undefined;
5
+ export declare function githubReleaseWorkflow(stack: Stack, root: string): string;
5
6
  export declare function githubWorkflow(stack: Stack, root: string): string;
6
7
  export declare function gitlabJob(stack: Stack, root: string): string;
7
8
  export interface CiResult {
@@ -1,6 +1,10 @@
1
1
  // `nikos ci setup`: run nikOS's checks on every pull request (GitHub) or merge request (GitLab).
2
2
  // The pipeline runs `nikos validate` and every story's scenarios with `nikos verify`, using the
3
3
  // nikos version that wrote it, so a red pipeline means the specs or the code broke, not nikos.
4
+ // A second, run-on-demand pipeline checks a release is ready to ship (nikos release check <n>
5
+ // --run): it fails while a story isn't done, its spec changed, or (when approval.policy is
6
+ // required-for-release) a story isn't approved. It's started by hand, with the release number,
7
+ // rather than on every change, because a release in progress is never ready.
4
8
  import { spawnSync } from "node:child_process";
5
9
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
6
10
  import { dirname, join } from "node:path";
@@ -20,12 +24,42 @@ export function detectHost(root) {
20
24
  return "gitlab";
21
25
  return undefined;
22
26
  }
27
+ /** The shell steps that install the project and its scenario runner, for a stack. */
28
+ const install = (stack, root) => stack === "python"
29
+ ? [existsSync(join(root, "requirements.txt")) ? "pip install -r requirements.txt" : "", "pip install behave"].filter(Boolean)
30
+ : [existsSync(join(root, "package-lock.json")) ? "npm ci" : "npm install"];
23
31
  /** The shell steps that install the runner and run the checks, for a stack. */
24
32
  function steps(stack, root, nikos) {
25
- const install = stack === "python"
26
- ? [existsSync(join(root, "requirements.txt")) ? "pip install -r requirements.txt" : "", "pip install behave"].filter(Boolean)
27
- : [existsSync(join(root, "package-lock.json")) ? "npm ci" : "npm install"];
28
- return [...install, `npx -y ${nikos} validate`, `npx -y ${nikos} verify`];
33
+ return [...install(stack, root), `npx -y ${nikos} validate`, `npx -y ${nikos} verify`];
34
+ }
35
+ /** The release check, run by hand with the release number. RELEASE comes in as an env var, never pasted into the script. */
36
+ const releaseSteps = (stack, root, nikos, variable) => [...install(stack, root), `npx -y ${nikos} release check "$${variable}" --run`];
37
+ export function githubReleaseWorkflow(stack, root) {
38
+ const nikos = `nikos@${version()}`;
39
+ const python = stack === "python" ? ` - uses: actions/setup-python@v5\n with:\n python-version: "3.12"\n` : "";
40
+ return `# Written by nikos ci setup: checks a release is ready to ship. Run it from the Actions tab
41
+ # (Run workflow) with the release number. It fails while a story isn't done, a spec changed
42
+ # after done, or a story needs an approval it doesn't have.
43
+ name: nikos release check
44
+ on:
45
+ workflow_dispatch:
46
+ inputs:
47
+ release:
48
+ description: The release number to check
49
+ required: true
50
+ type: number
51
+ jobs:
52
+ release-check:
53
+ runs-on: ubuntu-latest
54
+ env:
55
+ RELEASE: \${{ inputs.release }}
56
+ steps:
57
+ - uses: actions/checkout@v4
58
+ - uses: actions/setup-node@v4
59
+ with:
60
+ node-version: 22
61
+ ${python}${releaseSteps(stack, root, nikos, "RELEASE").map((s) => ` - run: ${s}`).join("\n")}
62
+ `;
29
63
  }
30
64
  export function githubWorkflow(stack, root) {
31
65
  const nikos = `nikos@${version()}`;
@@ -60,6 +94,15 @@ nikos:
60
94
  - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
61
95
  script:
62
96
  ${[...setup, ...steps(stack, root, nikos)].map((s) => ` - ${s}`).join("\n")}
97
+
98
+ # Checks a release is ready to ship. Run a pipeline by hand (Build > Pipelines > New pipeline)
99
+ # with the variable NIKOS_RELEASE set to the release number.
100
+ nikos-release:
101
+ image: ${image}
102
+ rules:
103
+ - if: $NIKOS_RELEASE
104
+ script:
105
+ ${[...setup, ...releaseSteps(stack, root, nikos, "NIKOS_RELEASE")].map((s) => ` - ${s}`).join("\n")}
63
106
  `;
64
107
  }
65
108
  const GITLAB_JOB = ".gitlab/ci/nikos.yml";
@@ -84,17 +127,22 @@ export function setupCi(root, opts = {}) {
84
127
  };
85
128
  if (host === "github") {
86
129
  write(".github/workflows/nikos.yml", githubWorkflow(stack, root));
130
+ write(".github/workflows/nikos-release.yml", githubReleaseWorkflow(stack, root));
87
131
  return { host, created, skipped };
88
132
  }
89
133
  // GitLab reads one .gitlab-ci.yml, so the job lives in its own file and is included from it.
90
134
  write(GITLAB_JOB, gitlabJob(stack, root));
135
+ // A job file from before the release check: say what to add rather than rewrite it.
136
+ const old = skipped.includes(GITLAB_JOB) && !readFileSync(join(root, GITLAB_JOB), "utf8").includes("nikos-release:");
137
+ const releaseTodo = old ? `Add the release check job to ${GITLAB_JOB}:\n${gitlabJob(stack, root).slice(gitlabJob(stack, root).indexOf("# Checks a release"))}` : undefined;
91
138
  const main = join(root, ".gitlab-ci.yml");
92
139
  if (!existsSync(main)) {
93
140
  write(".gitlab-ci.yml", INCLUDE);
94
- return { host, created, skipped };
141
+ return { host, created, skipped, ...(releaseTodo ? { todo: releaseTodo } : {}) };
95
142
  }
96
143
  const included = readFileSync(main, "utf8").includes(GITLAB_JOB);
97
- return { host, created, skipped, todo: included ? undefined : `Add this to .gitlab-ci.yml:\n${INCLUDE}` };
144
+ const todo = [included ? "" : `Add this to .gitlab-ci.yml:\n${INCLUDE}`, releaseTodo ?? ""].filter(Boolean).join("\n");
145
+ return { host, created, skipped, ...(todo ? { todo } : {}) };
98
146
  }
99
147
  export function runCi(args) {
100
148
  const [sub, ...rest] = args;
@@ -114,6 +162,9 @@ export function runCi(args) {
114
162
  if (r.todo)
115
163
  console.log(`\n${r.todo}`);
116
164
  console.log(`\nEvery ${r.host === "github" ? "pull request" : "merge request"} now runs nikos validate and every story's scenarios.`);
165
+ console.log(r.host === "github"
166
+ ? "To check a release is ready to ship, run the \"nikos release check\" workflow from the Actions tab with its number."
167
+ : "To check a release is ready to ship, run a pipeline by hand with NIKOS_RELEASE set to its number.");
117
168
  return 0;
118
169
  }
119
170
  catch (err) {
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.
@@ -126,6 +143,12 @@ name fields again on 2026-11-05 (no page showed a version):
126
143
  - Codex: https://developers.openai.com/plugins/build/plugins. The plugin
127
144
  title is `interface.displayName` in `.codex-plugin/plugin.json`. The
128
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).
129
152
 
130
153
  All three find skills as `skills/<name>/SKILL.md` at the plugin root.
131
154
  `test/install.test.ts` checks the manifests agree with each other and with
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nikos",
3
- "version": "0.2.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",
@@ -56,3 +56,10 @@ GitLab CI (`.gitlab/ci/nikos.yml`, included from `.gitlab-ci.yml`). Pass
56
56
  `--github` or `--gitlab` when there's no remote yet. If the repo already has a
57
57
  `.gitlab-ci.yml`, it prints the `include:` lines to add: add them, and show the
58
58
  person the change.
59
+
60
+ It also adds a release check the person runs by hand when a release is meant to
61
+ ship: the "nikos release check" workflow on GitHub (Actions tab, Run workflow,
62
+ with the release number), or on GitLab a pipeline run with `NIKOS_RELEASE` set to
63
+ it. It fails while a story isn't done, a spec changed after done, or (when
64
+ `approval.policy` is `required-for-release`) a story isn't approved. Tell the
65
+ person how to run it; don't run it on every change.