diff --git a/.cursor/rules/no-auto-commit.mdc b/.cursor/rules/no-auto-commit.mdc
new file mode 100644
index 0000000..ec6ab3d
--- /dev/null
+++ b/.cursor/rules/no-auto-commit.mdc
@@ -0,0 +1,14 @@
+---
+description: Never commit or push without explicit user instruction.
+alwaysApply: true
+---
+
+# No auto-commit
+
+Never run `git commit`, `git push`, `git tag`, or `npm publish` unless the user explicitly asks (e.g. "commit this", "push", "publish").
+
+- Show the user what changed and let them decide.
+- Use `git diff` or `git status` output to summarise changes in your response instead.
+- If you are about to perform a git write operation, stop and confirm first.
+
+This applies regardless of how small, clean, or obviously-correct the change appears.
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..d5cc121
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,3 @@
+# Normalize line endings to LF everywhere. A CRLF shebang in scripts/init.mjs
+# would break the published bin on Unix, so this is a correctness guard, not style.
+* text=auto eol=lf
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7a907ed..8e48f1a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -6,11 +6,46 @@ on:
branches: [main]
jobs:
- smoke-test:
+ checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
- node-version: 20
+ node-version: 22
+ - name: Syntax-check scripts
+ run: |
+ node --check scripts/init.mjs
+ node --check scripts/smoke-test.mjs
+ - name: Validate package.json parses
+ run: node -e "JSON.parse(require('fs').readFileSync('package.json','utf8'))"
+ - name: Preview npm package contents
+ run: npm run pack:dry-run
+
+ - name: E2E — install from tarball and run the bin
+ run: |
+ npm pack
+ mkdir -p /tmp/e2e/proj
+ cd /tmp/e2e
+ npm init -y > /dev/null
+ npm install "$GITHUB_WORKSPACE"/cursor-os-*.tgz
+ ./node_modules/.bin/cursor-os init --target ./proj
+ ./node_modules/.bin/cursor-os doctor --target ./proj
+ # bare invocation must print help and write nothing
+ ./node_modules/.bin/cursor-os | grep -q "Usage:"
+ # programmatic import must resolve
+ node -e "import('cursor-os').then(m => { if (typeof m.install !== 'function') process.exit(1); }).catch(() => process.exit(1))"
+
+ smoke-test:
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, windows-latest]
+ node: [20, 22]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: ${{ matrix.node }}
- run: npm test
diff --git a/.gitignore b/.gitignore
index fe32485..8a3c2b7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,3 +17,15 @@ Thumbs.db
.tmp/
tmp/
*.local
+
+# npm pack output
+*.tgz
+
+# cursor-os installed on itself (dogfooded). The files below are byte-for-byte
+.cursor/agents/
+.cursor/skills/
+.cursor/.cursor-os-version
+.cursor/rules/core.mdc
+.cursor/rules/debugging.mdc
+.cursor/rules/frontend.mdc
+prompts/
diff --git a/AGENTS.md b/AGENTS.md
index 37d42ec..31de595 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -14,6 +14,7 @@ This repository ships Cursor OS — an installable operating layer that makes Cu
- `template/` — the kit that gets copied into other projects. Never assume a specific stack here.
- `scripts/` — the installer (`init.mjs`) and its smoke test. Node built-ins only; no dependencies.
- `examples/` — concrete examples showing what localization looks like.
+- `docs/` — this repo's own decision log (not installed into user projects).
- `README.md`, `CHANGELOG.md`, `CONTRIBUTING.md` — public-facing docs.
## Working agreements
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a41cd0d..f1062f5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,8 +6,37 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
+
+
+## [0.2.0] — 2026-06-10
+
+First npm release: `npx cursor-os init`.
+
### Added
+- `init` now runs a post-install health check automatically: it reports missing files, the remaining placeholder count, and the exact next step (including a Cursor CLI one-liner for running localization).
+- `doctor` now reports version drift between the installed `.cursor/.cursor-os-version` marker and the current Cursor OS version, and suggests re-running `init` to pick up new files.
+- Smoke tests for the localized success path ("installed and localized"), version-drift reporting, the post-install check, and the new bare-invocation behavior (126 checks).
+- CI hardening: smoke tests run on Node 20 and 22 across Ubuntu and Windows; a separate job syntax-checks the scripts, validates `package.json` parses, and previews the npm package contents.
+- `docs/decision-log.md` recording the CLI default, upgrade-semantics, Node-version, and localization-detection decisions.
+- Programmatic API: `import { install, doctor } from "cursor-os"` now resolves via the package `exports` field.
+- Runtime Node version guard: the CLI fails fast with a clear message on Node older than 20 (the `engines` field is advisory only).
+- CI end-to-end packaging test: packs the tarball, installs it into a scratch project, and runs the `cursor-os` bin (`init`, `doctor`, bare help, and programmatic import).
+- `.gitattributes` enforcing LF line endings — a CRLF shebang would break the published bin on Unix.
+- `.gitignore` entry for `*.tgz` (local `npm pack` output).
+
+### Changed (breaking, pre-publish)
+
+- A command (`init` or `doctor`) is now required whenever arguments are given. Bare invocation with no arguments prints help and writes nothing — `npx cursor-os` will never modify the filesystem by accident. The previous default-to-`init` behavior (including `node scripts/init.mjs
`) is removed; use `init ` or `init --target `.
+- Minimum supported Node.js version raised from 18 (end-of-life) to 20 (`engines` field).
+
+### Fixed
+
+- `doctor` could never report "installed and localized": the install-time instruction notes in `template/AGENTS.md` and `template/docs/repo-memory.md` contained the literal word "TODO", so the placeholder count never reached zero. The notes no longer use the word, and the localization prompt now instructs deleting them when done. Fresh-install placeholder counts changed from 5/12 to 4/10.
+- CLI direct-invocation guard now realpath-normalizes both paths, so the CLI runs correctly when invoked through a symlinked path (e.g. macOS `/tmp` → `/private/tmp`).
+- `listFiles` uses `lstat` so a symlinked directory inside `template/` can no longer cause infinite recursion.
+- Removed an unused variable in the smoke test; hoisted the doctor placeholder-file list to a module constant.
+
- GitHub Actions smoke-test workflow.
- Pull request and issue templates for public contributions.
- README badges and unofficial-project disclaimer.
@@ -31,10 +60,10 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- `03-debug-regression.md` → `prompts/debug-regression.md`
- `04-pr-review.md` → `prompts/review-pr.md`
- All template references to numbered prompt filenames updated.
-- CLI restructured with explicit `init` and `doctor` subcommands; bare invocation still defaults to `init`.
+- CLI restructured with explicit `init` and `doctor` subcommands.
- CLI argument parsing hardened: unknown commands, unknown options, invalid `--target`, and `doctor --dry-run` now fail without writing files.
- `doctor` now checks the full installed template file set plus the version marker, not just core files.
-- Smoke test updated for new prompt file names and extended with doctor and subprocess CLI tests (112 checks).
+- Smoke test updated for new prompt file names and extended with doctor and subprocess CLI tests.
- `package.json` description updated; `examples/` and public-release docs added to published files list.
- `CONTRIBUTING.md` updated with prompt naming convention and test-sync guidance.
- `AGENTS.md` updated with `examples/` in the layout and prompt naming rule.
@@ -42,7 +71,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
### Not done yet
-- npm publishing (`npx cursor-os init`) — planned for `v0.2`.
+- Interactive setup with project detection and stack presets (Next.js, Supabase, Vercel) — planned for a future release.
## [0.1.0] — 2026-06-02
diff --git a/README.md b/README.md
index 9575266..e4eb70e 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
An installable operating layer that makes Cursor project-aware.
[](https://github.com/KingEmma7/cursor-os/actions/workflows/ci.yml)
-[](package.json)
+[](package.json)
[](LICENSE)
> **Unofficial project.** Cursor OS is a community-maintained installable layer for Cursor. It is not affiliated with, endorsed by, or maintained by Cursor or Anysphere.
@@ -50,9 +50,8 @@ The installer copies these files. The localization prompt fills them in for your
**Step 1 — Install the base OS** (the installer does this):
```bash
-git clone https://github.com/KingEmma7/cursor-os.git ~/cursor-os
cd /path/to/your-project
-node ~/cursor-os/scripts/init.mjs init
+npx cursor-os init
```
This gives you the structure. The files contain TODO placeholders — Cursor knows to use them, but they don't yet describe your project.
@@ -71,13 +70,13 @@ Cursor OS is designed to drop into any project at any stage — greenfield or ma
```bash
# From the root of any existing project
-node /path/to/cursor-os/scripts/init.mjs init
+npx cursor-os init
# Preview what would be installed first
-node /path/to/cursor-os/scripts/init.mjs init --dry-run
+npx cursor-os init --dry-run
# Check if Cursor OS is already installed
-node /path/to/cursor-os/scripts/init.mjs doctor
+npx cursor-os doctor
```
For new repos, create your project normally first, then run the installer from the project root. This repository's root is the Cursor OS source project, not the installed project layout.
@@ -91,31 +90,33 @@ After localization:
- When you paste `prompts/plan-feature.md` into Cursor and describe a feature, the plan references your actual architecture and patterns without you explaining them.
- When you paste `prompts/implement-change.md`, Cursor follows your conventions without being told.
-Use the Cursor OS checkout to check the installation state:
+Check the installation state of any project:
```bash
-node ~/cursor-os/scripts/init.mjs doctor --target /path/to/your-project
+npx cursor-os doctor --target /path/to/your-project
```
Example output:
```
-Cursor OS v0.1.0 — doctor
+Cursor OS vX.Y.Z — doctor
Target: /path/to/your-project
ok .cursor/agents/verifier.md
ok .cursor/rules/core.mdc
ok .cursor/skills/implementation-loop/SKILL.md
ok AGENTS.md
- note: 5 TODO placeholder(s) remain — run prompts/localize-cursor-os.md
+ note: 4 TODO placeholder(s) remain — run prompts/localize-cursor-os.md
ok docs/quality-rubric.md
ok docs/repo-memory.md
- note: 12 TODO placeholder(s) remain — run prompts/localize-cursor-os.md
+ note: 10 TODO placeholder(s) remain — run prompts/localize-cursor-os.md
ok prompts/localize-cursor-os.md
ok .cursor/.cursor-os-version
```
-(Abbreviated — `doctor` lists every installed file. The `note:` lines flag the unfilled TODO placeholders in `AGENTS.md` and `docs/repo-memory.md` that localization resolves.)
+(Abbreviated — `doctor` lists every installed file; the version shown matches your checkout. The `note:` lines flag the unfilled TODO placeholders in `AGENTS.md` and `docs/repo-memory.md` that localization resolves. Once localization fills them, `doctor` reports "installed and localized". If the install came from an older Cursor OS version, `doctor` also notes the drift so you can re-run `init` to pick up new files.)
+
+`init` runs this same health check automatically after installing, so you always see the placeholder count and the next step without a separate command.
## What Cursor loads automatically vs. what you paste
@@ -145,9 +146,9 @@ See the [prompts guide](template/prompts/README.md) (installs as `prompts/README
Custom instruction sets or system-prompt files (sometimes called "behavioral guideline packs") tell Cursor how to behave generically. Cursor OS does something different: it tells Cursor about *this* project specifically. The two are complementary. Cursor OS files live in the repo, travel with the code, and get updated as the project evolves.
-## Current status
+## Installing from a checkout
-Cursor OS is **not published to npm yet**. For v0.1, clone the repo and run the installer script directly.
+If you prefer not to use npm, clone the repo and run the installer script directly — it behaves identically:
```bash
git clone https://github.com/KingEmma7/cursor-os.git ~/cursor-os
@@ -155,17 +156,15 @@ cd your-project
node ~/cursor-os/scripts/init.mjs init
```
-`npx cursor-os init` is planned for a future release.
-
-Before publishing, run through [`RELEASE_CHECKLIST.md`](RELEASE_CHECKLIST.md). The checklist covers release readiness, package metadata, installer checks, template quality, and `npm pack --dry-run`.
+Maintainers: before tagging a release, run through [`RELEASE_CHECKLIST.md`](RELEASE_CHECKLIST.md).
## CLI reference
```
-node scripts/init.mjs [command] [target] [options]
+npx cursor-os [target] [options]
Commands:
- init Install Cursor OS into the target directory (default)
+ init Install Cursor OS into the target directory
doctor Check whether Cursor OS is installed in the target directory
Options:
@@ -175,6 +174,8 @@ Options:
-h, --help Show this help
```
+A command is required: bare invocation (`npx cursor-os` with no arguments) prints help and never writes files. For a target directory named `init` or `doctor`, or one whose name starts with `-`, use the explicit form `init --target `. Requires Node.js 20 or newer.
+
## What gets installed
```
@@ -217,8 +218,9 @@ prompts/
## Roadmap
-- `v0.1` — installable operating layer: contract, rules, skills, verifier, docs, prompts, installer, doctor command.
-- `v0.2` — npm publishing (`npx cursor-os init`), interactive setup with project detection, stack presets (Next.js, Supabase, Vercel).
+- `v0.1` — installable operating layer: contract, rules, skills, verifier, docs, prompts, installer, doctor command. ✅
+- `v0.2` — npm publishing (`npx cursor-os init`), safer CLI defaults, post-install health check, version-drift detection. ✅
+- Next — interactive setup with project detection, stack presets (Next.js, Supabase, Vercel).
## Contributing
diff --git a/RELEASE_CHECKLIST.md b/RELEASE_CHECKLIST.md
index 0813fef..594275c 100644
--- a/RELEASE_CHECKLIST.md
+++ b/RELEASE_CHECKLIST.md
@@ -22,6 +22,7 @@ Use this checklist before tagging a public release or publishing Cursor OS to np
## Installer and CLI
- [ ] `node scripts/init.mjs --help` shows `init`, `doctor`, `--target`, `--dry-run`, and `--version`.
+- [ ] `node scripts/init.mjs` with no arguments prints help and writes nothing.
- [ ] `node scripts/init.mjs --version` matches `package.json`.
- [ ] `node scripts/init.mjs init --dry-run --target ` writes nothing.
- [ ] `node scripts/init.mjs init --target ` installs the expected file set.
@@ -41,16 +42,26 @@ Use this checklist before tagging a public release or publishing Cursor OS to np
- [ ] `npm test` passes.
- [ ] `node -e "JSON.parse(require('fs').readFileSync('package.json','utf8'))"` succeeds.
+- [ ] No hardcoded version strings in `README.md` or `examples/` (the badge is dynamic; example outputs use `vX.Y.Z`). Versions appear only in `package.json` and `CHANGELOG.md`.
- [ ] Final old-branding search has no matches outside historical changelog context.
- [ ] Final search has no numbered prompt references outside historical changelog context.
+## Packaging integrity
+
+- [ ] `npm pack`, install the tarball into a scratch project, and run the bin: `init`, `doctor`, and bare invocation all behave. (CI runs this on every push.)
+- [ ] `import('cursor-os')` resolves and exposes `install` and `doctor`.
+- [ ] `head -1 scripts/init.mjs` is exactly `#!/usr/bin/env node` (no CRLF, no BOM).
+- [ ] No stray `*.tgz` files tracked in git.
+
## npm publishing
Only after all previous sections pass:
+- [ ] npm account has 2FA enabled (at minimum for writes).
- [ ] Confirm the intended version.
- [ ] Update `CHANGELOG.md` with release date.
- [ ] Create a git tag for the release.
- [ ] Run `npm publish --dry-run`.
- [ ] Run `npm publish` only when intentionally publishing.
-- [ ] After publish, update README install examples from local script usage to `npx cursor-os init`.
+- [ ] README install examples use the `npx cursor-os` form (done in the release commit, not after).
+- [ ] After publish, verify with `npm view cursor-os` and `npx cursor-os@latest init --dry-run --target `.
diff --git a/docs/decision-log.md b/docs/decision-log.md
new file mode 100644
index 0000000..5c5f746
--- /dev/null
+++ b/docs/decision-log.md
@@ -0,0 +1,45 @@
+# Decision Log — cursor-os (this repo)
+
+Append-only record of notable decisions for the Cursor OS project itself. Newest first. (Not part of the installable template — `template/docs/decision-log.md` is what gets installed into user projects.)
+
+## 2026-06-10 — v0.2.0 publishes manually; CI publishing with provenance deferred
+
+- **Decision:** The first npm release is published manually by the maintainer (`npm publish` from a tagged, CI-green commit). Automated publishing from GitHub Actions with npm provenance/trusted publishing is deferred.
+- **Context:** Provenance attestation requires publishing from CI with OIDC; setting that up needs npm-side trusted-publisher configuration that doesn't exist yet. Shipping v0.2.0 should not block on it.
+- **Alternatives:** A release workflow gated on tags with `NPM_TOKEN` — rejected for now: a half-configured workflow that fails on first use is worse than a documented manual process. CI already verifies the exact tarball end-to-end on every push.
+- **Consequences:** No provenance badge on the first release. Revisit before v0.3: configure npm trusted publishing and add a tag-triggered release workflow with `--provenance`.
+
+## 2026-06-10 — Localization detection stays a placeholder-marker heuristic
+
+- **Decision:** `doctor` detects unfinished localization by counting `\bTODO\b` matches in `AGENTS.md` and `docs/repo-memory.md`. Instructional prose in those template files must not contain the literal word "TODO", and the localization prompt instructs deleting the install-time notes when done.
+- **Context:** The previous instructional text contained "TODO", making the "installed and localized" state unreachable.
+- **Alternatives:** A structured flag (e.g. `localized: true` in the version marker) — rejected for now: it adds write-state the agent must remember to set, while the heuristic is self-healing and matches what users see in the files.
+- **Consequences:** Template prose edits must avoid the bare word "TODO" outside real placeholders. The localized-success smoke test guards this.
+
+## 2026-06-10 — Bare invocation prints help; a command is required
+
+- **Decision:** `node scripts/init.mjs` (and post-publish `npx cursor-os`) with no arguments prints help and writes nothing. `init` must be explicit. Supplying arguments without a command is an error.
+- **Context:** The CLI previously defaulted to `init` into the current directory, so an exploratory bare run wrote 19 files wherever the user happened to be.
+- **Alternatives:** Keep default-to-init (friendlier for the documented happy path) — rejected: a default action that mutates the filesystem is the wrong failure mode for a tool about to be published to npm.
+- **Consequences:** Breaking pre-publish CLI change; README, help text, smoke tests, and CHANGELOG updated together. `init` compensates with an automatic post-install health check so the explicit command costs nothing in guidance.
+
+## 2026-06-10 — Upgrade semantics: skip-existing stays; doctor reports drift
+
+- **Decision:** Re-running `init` over an existing install continues to only add missing files and refresh the version marker — never overwrite. `doctor` compares the marker version to the current package version and notes drift, recommending a re-run of `init`.
+- **Context:** v0.2 will introduce new/changed template files; users need a signal without risking their localized content.
+- **Alternatives:** A diff/merge upgrade mode — deferred: high complexity, and localized files cannot be machine-merged safely. May revisit with a three-way-merge or `--force-file` flag if real demand appears.
+- **Consequences:** Users on old templates must consciously re-run `init`; changed (not just added) template files won't propagate. Documented in README.
+
+## 2026-06-10 — Node.js floor raised to 20; CI tests 20/22 on Ubuntu and Windows
+
+- **Decision:** `engines.node >= 20`. CI matrix: Node 20 and 22 × ubuntu-latest and windows-latest, plus a checks job (`node --check`, `package.json` parse, `npm pack --dry-run`).
+- **Context:** Node 18 is end-of-life; CI previously tested only Node 20 on Ubuntu while claiming `>=18` support, and the release checklist's machine-checkable items weren't enforced.
+- **Alternatives:** Keep `>=18` for reach — rejected: claiming support for an EOL version CI doesn't test is a promise the project can't keep.
+- **Consequences:** Slightly narrower install base; honest support claims; checklist items can no longer be skipped by a human.
+
+## 2026-06-10 — Localization is not auto-executed by the installer
+
+- **Decision:** The installer never invokes an AI to localize. Instead, `init` ends with an automatic doctor pass (placeholder count + next step) and prints a Cursor CLI one-liner (`cursor-agent -p "$(cat prompts/localize-cursor-os.md)"`) as an opt-in tip.
+- **Context:** Proposal to run localization automatically as part of `npx cursor-os init`.
+- **Alternatives:** Shell out to `cursor-agent` when detected — rejected for v0.x: spends the user's tokens without consent, produces unreviewed edits, and adds an external dependency to a deliberately dependency-free script.
+- **Consequences:** Setup remains two steps, but the second step is now printed at the exact moment it's needed. Revisit as an explicit `--localize` opt-in flag for v0.2+.
diff --git a/docs/quality-rubric.md b/docs/quality-rubric.md
new file mode 100644
index 0000000..bfc260e
--- /dev/null
+++ b/docs/quality-rubric.md
@@ -0,0 +1,43 @@
+# Quality Rubric
+
+Gate to check before claiming any change is done. All items must pass with evidence.
+
+## Correctness
+
+- [ ] Meets the stated acceptance criteria — the actual request, not an adjacent one.
+- [ ] Edge cases and invalid input handled, not just the happy path.
+- [ ] Error and empty states are handled deliberately, not left to crash or swallow silently.
+
+## Fit
+
+- [ ] Reuses existing patterns and utilities; no parallel abstractions invented.
+- [ ] Changes are surgical and scoped to the task; no unrelated refactors mixed in.
+- [ ] Zero new dependencies added without explicit justification (this repo is zero-dep by design).
+
+## Maintainability
+
+- [ ] The next developer can understand where the change lives and why.
+- [ ] New abstractions named clearly with one responsibility.
+- [ ] Complex decisions recorded in `docs/decision-log.md`.
+
+## Safety
+
+- [ ] No-clobber invariant preserved: `install()` never overwrites existing user files.
+- [ ] `template/` remains framework-neutral; no stack-specific content added.
+- [ ] Template prose contains no bare "TODO" outside real placeholder markers.
+
+## Verification (project-specific)
+
+- [ ] `npm test` passes — `node scripts/smoke-test.mjs`, 126 checks minimum.
+- [ ] `node --check scripts/init.mjs && node --check scripts/smoke-test.mjs` clean.
+- [ ] `npm run pack:dry-run` exits 0 and file list matches `package.json#files`.
+- [ ] `node -e "JSON.parse(require('fs').readFileSync('package.json','utf8'))"` succeeds.
+- [ ] If `template/` files changed: `EXPECTED` in `smoke-test.mjs`, `README.md`, and `CHANGELOG.md` updated together.
+- [ ] If new template file added: `doctor` output reflects it (derived from `template/` at runtime).
+
+## Communication
+
+- [ ] Report covers what changed, how it was verified, and remaining risks.
+- [ ] `docs/repo-memory.md` updated if a durable fact changed.
+- [ ] `docs/decision-log.md` appended if a notable decision was made.
+- [ ] `CHANGELOG.md` updated for any user-visible change.
diff --git a/docs/repo-memory.md b/docs/repo-memory.md
new file mode 100644
index 0000000..24bd5ea
--- /dev/null
+++ b/docs/repo-memory.md
@@ -0,0 +1,59 @@
+# Repo Memory
+
+Durable facts about this repository. Keep it short, current, and true — stale memory is worse than none.
+
+## What this project is
+
+Cursor OS: an installable operating layer that makes Cursor project-aware. Developers run the installer into any repo, then run the localization prompt once in Cursor to fill in project-specific context. Published to npm as `cursor-os`.
+
+## Stack and key dependencies
+
+- **Language:** Node.js ESM (`.mjs`), zero runtime or dev dependencies by design
+- **Minimum Node:** 20 (enforced in `package.json` `engines`)
+- **Product:** `template/` (the installable kit) + `scripts/` (installer CLI)
+- **No build step:** scripts run directly with `node`
+
+## How it's organised
+
+```
+template/ The kit installed into user projects (AGENTS.md, rules, skills, agents, docs, prompts)
+scripts/ init.mjs (installer + library) and smoke-test.mjs (installer smoke test suite)
+examples/ Before/after localization walkthrough
+docs/ This repo's own decision log (not installed into user projects)
+.cursor/ cursor-os installed on itself (dogfooded)
+```
+
+## Commands that matter
+
+- Install: none (zero dependencies)
+- Test: `npm test`
+- Lint / syntax-check: `node --check scripts/init.mjs && node --check scripts/smoke-test.mjs`
+- Pack preview: `npm run pack:dry-run`
+- Dry-run install: `node scripts/init.mjs init --dry-run`
+- Health check: `node scripts/init.mjs doctor`
+- Build: none (no compile step)
+
+## Conventions and gotchas
+
+- **Zero deps, always.** The installer must stay dependency-free (Node built-ins only). No devDependencies without a very strong reason.
+- **Template files are framework-neutral.** No stack presets in `template/`; all project-specific content is generated during localization.
+- **No-clobber invariant.** `install()` never overwrites existing files. Every test that touches this path must preserve it.
+- **Idempotency.** Re-running `init` is always safe. Smoke tests verify second-run behaviour.
+- **EXPECTED list in smoke-test.mjs must stay in sync with template/.** When you add or remove a template file, update `EXPECTED` in `scripts/smoke-test.mjs`, `README.md`, and `CHANGELOG.md` together.
+- **Prompt files referenced by full path, never by number.** e.g. `prompts/plan-feature.md` not "prompt 1".
+- **Template prose must not contain the bare placeholder marker word outside real placeholders.** Instructional notes use alternative phrasing. This is what lets `doctor` reach its "installed and localized" success state.
+- **`docs/decision-log.md` at repo root** is this project's own decision record and is NOT the same as `template/docs/decision-log.md` (the blank one that gets installed into user projects).
+
+## Known constraints
+
+- Installer must remain dependency-free (`SECURITY.md:41`, root `AGENTS.md`)
+- Must never overwrite user files (core safety promise; most-tested behaviour in smoke suite)
+- Template must remain framework-neutral (no stack presets)
+- Bare `npx cursor-os` must never write files — command required
+- Node ≥ 20 required; no Node 18 support (EOL, decision logged 2026-06-10)
+
+## Recently learned facts
+
+- 2026-06-10 — `doctor`'s success state required rewriting template instruction notes so placeholder markers only appear as real placeholders. Fixed in audit remediation; the smoke test pins this.
+- 2026-06-10 — `listFiles` used `statSync` (follows symlinks); swapped to `lstatSync` to prevent cycle risk.
+- 2026-06-10 — CLI previously defaulted to `init`; now bare invocation prints help (breaking pre-publish, safe decision).
diff --git a/examples/localization-example.md b/examples/localization-example.md
index 24db889..cadf6c7 100644
--- a/examples/localization-example.md
+++ b/examples/localization-example.md
@@ -21,7 +21,7 @@ Cursor guesses at the routing style, invents an ORM method that doesn't match wh
You run:
```bash
-node ../cursor-os/scripts/init.mjs init
+npx cursor-os init
```
The following files are now in the project root:
@@ -48,19 +48,19 @@ prompts/update-repo-memory.md
At this point, Cursor sees the files, but `AGENTS.md` and `docs/repo-memory.md` contain only TODO markers. The OS is installed but not yet project-aware.
-Running `node scripts/init.mjs doctor` confirms:
+Running `npx cursor-os doctor` confirms:
```
-Cursor OS v0.1.0 — doctor
+Cursor OS vX.Y.Z — doctor
Target: /home/user/task-api
ok AGENTS.md
- note: 5 TODO placeholder(s) remain — run prompts/localize-cursor-os.md
+ note: 4 TODO placeholder(s) remain — run prompts/localize-cursor-os.md
ok .cursor/rules/core.mdc
ok .cursor/skills/implementation-loop/SKILL.md
ok .cursor/agents/verifier.md
ok docs/repo-memory.md
- note: 12 TODO placeholder(s) remain — run prompts/localize-cursor-os.md
+ note: 10 TODO placeholder(s) remain — run prompts/localize-cursor-os.md
ok docs/quality-rubric.md
ok prompts/localize-cursor-os.md
ok .cursor/.cursor-os-version
@@ -68,6 +68,12 @@ Target: /home/user/task-api
Cursor OS is installed. Run prompts/localize-cursor-os.md to complete setup.
```
+After localization fills the placeholders (and removes the install-time notes), the same command reports:
+
+```
+Cursor OS appears installed and localized.
+```
+
---
## Stage 3: After running the localization prompt
diff --git a/package.json b/package.json
index 6a6075d..b1b3ffa 100644
--- a/package.json
+++ b/package.json
@@ -1,11 +1,14 @@
{
"name": "cursor-os",
- "version": "0.1.0",
+ "version": "0.2.0",
"description": "An installable operating layer that makes Cursor project-aware: a portable engineering contract, rules, memory docs, and prompts that adapt to any repository.",
"type": "module",
"bin": {
"cursor-os": "./scripts/init.mjs"
},
+ "exports": {
+ ".": "./scripts/init.mjs"
+ },
"scripts": {
"init": "node scripts/init.mjs init",
"doctor": "node scripts/init.mjs doctor",
@@ -14,7 +17,7 @@
},
"files": [
"template",
- "scripts",
+ "scripts/init.mjs",
"examples",
"README.md",
"LICENSE",
@@ -38,7 +41,10 @@
"prompt-engineering",
"software-engineering"
],
- "author": "Emmanuel Tagbor",
+ "author": {
+ "name": "Emmanuel Tagbor",
+ "url": "https://github.com/KingEmma7"
+ },
"license": "MIT",
"repository": {
"type": "git",
@@ -49,6 +55,6 @@
},
"homepage": "https://github.com/KingEmma7/cursor-os#readme",
"engines": {
- "node": ">=18"
+ "node": ">=20"
}
}
diff --git a/scripts/init.mjs b/scripts/init.mjs
index 4f913ba..94b14fe 100755
--- a/scripts/init.mjs
+++ b/scripts/init.mjs
@@ -11,16 +11,20 @@ import {
mkdirSync,
copyFileSync,
readdirSync,
- statSync,
+ lstatSync,
+ realpathSync,
} from "node:fs";
import { fileURLToPath } from "node:url";
-import { dirname, join, relative } from "node:path";
+import { dirname, join, relative, resolve } from "node:path";
const __dirname = dirname(fileURLToPath(import.meta.url));
const repoRoot = join(__dirname, "..");
const templateDir = join(repoRoot, "template");
const MARKER_REL = join(".cursor", ".cursor-os-version");
+// Prose files doctor scans for unfilled placeholder markers.
+const TODO_FILES = ["AGENTS.md", join("docs", "repo-memory.md")];
+
function readVersion() {
try {
const pkg = JSON.parse(readFileSync(join(repoRoot, "package.json"), "utf8"));
@@ -37,10 +41,13 @@ function readVersion() {
* command: "init" | "doctor" | null
*
* Supported forms:
- * node init.mjs [init] [target] [--dry-run] [--target DIR]
+ * node init.mjs init [target] [--dry-run] [--target DIR]
* node init.mjs doctor [target] [--target DIR]
* node init.mjs --help | -h
* node init.mjs --version | -v
+ *
+ * A command is required when any other argument is given. A bare invocation
+ * with no arguments prints help — it never writes files.
*/
function parseArgs(argv) {
const args = {
@@ -49,6 +56,7 @@ function parseArgs(argv) {
target: process.cwd(),
help: false,
version: false,
+ bare: argv.length === 0,
errors: [],
};
let targetSet = false;
@@ -76,9 +84,8 @@ function parseArgs(argv) {
args.errors.push(`unknown option: ${a}`);
}
else if (!targetSet) {
- // Any bare, non-flag, non-subcommand word is a target path.
- // This preserves backwards-compat for `node init.mjs my-project` as well
- // as absolute or slash-prefixed paths.
+ // Any bare, non-flag, non-subcommand word is a target path
+ // (absolute, relative, or a plain directory name).
args.target = a;
targetSet = true;
} else {
@@ -86,9 +93,10 @@ function parseArgs(argv) {
}
}
- // Default command
- if (args.command === null && !args.help && !args.version) {
- args.command = "init";
+ // A command is required whenever arguments are given. Bare invocation
+ // (no args at all) falls through to help so `npx cursor-os` is read-only.
+ if (args.command === null && !args.help && !args.version && !args.bare) {
+ args.errors.push("missing command: specify 'init' or 'doctor'");
}
if (args.command === "doctor" && args.dryRun) {
@@ -102,10 +110,10 @@ function parseArgs(argv) {
const HELP = `Cursor OS — installer
Usage:
- node scripts/init.mjs [command] [target] [options]
+ cursor-os [target] [options]
Commands:
- init Install Cursor OS into the target directory (default)
+ init Install Cursor OS into the target directory
doctor Check whether Cursor OS is installed in the target directory
Arguments:
@@ -118,12 +126,17 @@ Options:
-h, --help Show this help
Examples:
- node scripts/init.mjs
- node scripts/init.mjs init
- node scripts/init.mjs init --dry-run
- node scripts/init.mjs init --target ./my-project
- node scripts/init.mjs doctor
- node scripts/init.mjs doctor --target ./my-project
+ cursor-os init
+ cursor-os init --dry-run
+ cursor-os init --target ./my-project
+ cursor-os doctor
+ cursor-os doctor --target ./my-project
+
+Notes:
+ A command is required; bare invocation prints this help and writes nothing.
+ For a target directory named "init" or "doctor", or one starting with "-",
+ use the explicit form: init --target .
+ When running from a local checkout: node scripts/init.mjs
The installer copies AGENTS.md, .cursor/, docs/, and prompts/ into the target.
It never overwrites existing user files — it skips them and reports.
@@ -131,12 +144,17 @@ After installing, open Cursor and run prompts/localize-cursor-os.md.`;
// ── File helpers ──────────────────────────────────────────────────────────────
-/** Recursively collect files under dir as paths relative to dir. */
+/**
+ * Recursively collect files under dir as paths relative to dir.
+ * Uses lstat so symlinked directories are not recursed into (prevents cycles).
+ * Symlinks to files are included and copied as their target's content by
+ * copyFileSync; symlinks to directories are skipped (not recursed, not copied).
+ */
function listFiles(dir) {
const out = [];
for (const entry of readdirSync(dir)) {
const full = join(dir, entry);
- if (statSync(full).isDirectory()) {
+ if (lstatSync(full).isDirectory()) {
for (const child of listFiles(full)) out.push(join(entry, child));
} else {
out.push(entry);
@@ -207,9 +225,19 @@ function doctorChecks() {
];
}
+/** Read the installed version from the marker file, or null if unreadable. */
+function readMarkerVersion(target) {
+ try {
+ const content = readFileSync(join(target, MARKER_REL), "utf8");
+ return content.match(/cursor-os (\S+)/)?.[1] ?? null;
+ } catch {
+ return null;
+ }
+}
+
/**
* Check whether Cursor OS appears installed in target.
- * Returns { checks: [{label, present, note}], todoCount, target }.
+ * Returns { checks: [{label, present, note}], todoCount, markerVersion, target }.
* Never modifies files.
*/
export function doctor({ target } = {}) {
@@ -221,17 +249,14 @@ export function doctor({ target } = {}) {
let note = null;
let todoCount = 0;
- if (present) {
- // Flag unfilled TODO placeholders in key prose files
- const TODO_FILES = ["AGENTS.md", join("docs", "repo-memory.md")];
- if (TODO_FILES.includes(rel)) {
- try {
- const content = readFileSync(fullPath, "utf8");
- todoCount = (content.match(/\bTODO\b/g) ?? []).length;
- if (todoCount > 0) note = `${todoCount} TODO placeholder(s) remain — run prompts/localize-cursor-os.md`;
- } catch {
- // ignore read errors
- }
+ // Flag unfilled TODO placeholders in key prose files
+ if (present && TODO_FILES.includes(rel)) {
+ try {
+ const content = readFileSync(fullPath, "utf8");
+ todoCount = (content.match(/\bTODO\b/g) ?? []).length;
+ if (todoCount > 0) note = `${todoCount} TODO placeholder(s) remain — run prompts/localize-cursor-os.md`;
+ } catch {
+ // ignore read errors
}
}
@@ -239,7 +264,7 @@ export function doctor({ target } = {}) {
});
const todoCount = checks.reduce((n, c) => n + c.todoCount, 0);
- return { checks, todoCount, target };
+ return { checks, todoCount, markerVersion: readMarkerVersion(target), target };
}
// ── CLI entry point ───────────────────────────────────────────────────────────
@@ -266,9 +291,31 @@ function runInit(args) {
if (args.dryRun) {
console.log("\nDry run complete — no files were written.");
+ return;
+ }
+
+ // Post-install health check: confirm the install and surface what
+ // localization still needs to fill in, so the next step is unmissable.
+ const health = doctor({ target: args.target });
+ const missing = health.checks.filter((c) => !c.present).length;
+ // Show a relative path only when the target is inside this checkout.
+ const rel = relative(repoRoot, args.target);
+ let where = rel || "this repo";
+ if (rel.startsWith("..")) where = resolve(args.target);
+
+ console.log("\nPost-install check:");
+ if (missing > 0) {
+ console.log(` ${missing} expected file(s) missing — run: cursor-os doctor --target ${args.target}`);
+ } else if (health.todoCount > 0) {
+ console.log(` All files installed. ${health.todoCount} placeholder(s) await localization.`);
} else {
- const where = relative(repoRoot, args.target) || "this repo";
- console.log(`\nDone. Next: open Cursor and run prompts/localize-cursor-os.md to adapt the OS to ${where}.`);
+ console.log(" All files installed and localized.");
+ }
+
+ if (health.todoCount > 0) {
+ console.log(`\nNext: open Cursor in ${where} and run prompts/localize-cursor-os.md to adapt the OS to your project.`);
+ console.log('Tip: with the Cursor CLI installed you can run it directly:');
+ console.log(' cursor-agent -p "$(cat prompts/localize-cursor-os.md)"');
}
}
@@ -286,18 +333,36 @@ function runDoctor(args) {
if (!present) allPresent = false;
}
+ if (result.markerVersion && result.markerVersion !== version) {
+ console.log(`\n note: installed from cursor-os ${result.markerVersion}; current is ${version}.`);
+ console.log(" Re-run init to add any files introduced since (existing files are never overwritten).");
+ }
+
console.log("");
if (allPresent && result.todoCount === 0) {
console.log("Cursor OS appears installed and localized.");
} else if (allPresent) {
console.log("Cursor OS is installed. Run prompts/localize-cursor-os.md to complete setup.");
} else {
- console.log("Cursor OS is not fully installed. Run: node scripts/init.mjs init");
+ console.log("Cursor OS is not fully installed. Run: cursor-os init");
process.exitCode = 1;
}
}
+// Minimum supported Node major version. Keep in sync with package.json engines.
+const MIN_NODE_MAJOR = 20;
+
function main() {
+ // engines in package.json is advisory only — fail fast with a clear message.
+ const nodeMajor = Number(process.versions.node.split(".")[0]);
+ if (nodeMajor < MIN_NODE_MAJOR) {
+ console.error(
+ `Error: cursor-os requires Node.js ${MIN_NODE_MAJOR} or newer (you are running ${process.versions.node}).`,
+ );
+ process.exitCode = 1;
+ return;
+ }
+
const args = parseArgs(process.argv.slice(2));
if (args.errors.length) {
@@ -312,7 +377,7 @@ function main() {
return;
}
- if (args.help) {
+ if (args.help || args.bare) {
console.log(HELP);
return;
}
@@ -330,6 +395,19 @@ function main() {
}
// Only run main when invoked directly, not when imported by the smoke test.
-if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
+// realpathSync normalizes symlinks (e.g. macOS /tmp → /private/tmp).
+function isDirectInvocation() {
+ if (!process.argv[1]) return false;
+ try {
+ return (
+ realpathSync(fileURLToPath(import.meta.url)) ===
+ realpathSync(process.argv[1])
+ );
+ } catch {
+ return fileURLToPath(import.meta.url) === process.argv[1];
+ }
+}
+
+if (isDirectInvocation()) {
main();
}
diff --git a/scripts/smoke-test.mjs b/scripts/smoke-test.mjs
index 800d998..decbebc 100755
--- a/scripts/smoke-test.mjs
+++ b/scripts/smoke-test.mjs
@@ -240,6 +240,73 @@ withTempDir((dir) => {
check("doctor reports actual TODO placeholder count", result.todoCount > 0);
});
+// 8b. doctor — a fully localized install reaches the success state.
+console.log("\ndoctor (localized install):");
+withTempDir((dir) => {
+ install({ target: dir });
+ // Simulate localization: real content, no placeholder markers left.
+ writeFileSync(
+ join(dir, "AGENTS.md"),
+ [
+ "# AGENTS.md",
+ "",
+ "## Project context",
+ "",
+ "- **What this is:** Internal task API for ops teams.",
+ "- **Stack:** Node.js 22, Fastify, PostgreSQL 16, Fly.io.",
+ "- **How it's organized:** See docs/architecture.md.",
+ "- **Build / test / run commands:** npm install / npm test / npm run dev.",
+ "",
+ ].join("\n"),
+ "utf8",
+ );
+ writeFileSync(
+ join(dir, "docs", "repo-memory.md"),
+ [
+ "# Repo Memory",
+ "",
+ "## What this project is",
+ "",
+ "Internal task-management REST API for operations teams.",
+ "",
+ "## Commands that matter",
+ "",
+ "- Install: npm install",
+ "- Test: npm test",
+ "",
+ ].join("\n"),
+ "utf8",
+ );
+
+ const result = doctor({ target: dir });
+ check("doctor reports zero placeholders on a localized install", result.todoCount === 0);
+ check("doctor still reports all files present", result.checks.every((c) => c.present));
+
+ const cli = runCli(["doctor", "--target", dir]);
+ check("CLI doctor localized install exits 0", cli.status === 0);
+ check(
+ "CLI doctor localized install reports installed and localized",
+ cli.stdout.includes("installed and localized"),
+ );
+});
+
+// 8c. doctor — reports version drift between the marker and current package.
+console.log("\ndoctor (version drift):");
+withTempDir((dir) => {
+ install({ target: dir });
+ writeFileSync(join(dir, ".cursor", ".cursor-os-version"), "cursor-os 0.0.1\n", "utf8");
+
+ const result = doctor({ target: dir });
+ check("doctor exposes the installed marker version", result.markerVersion === "0.0.1");
+
+ const cli = runCli(["doctor", "--target", dir]);
+ check("CLI doctor exits 0 despite version drift", cli.status === 0);
+ check(
+ "CLI doctor notes the version drift",
+ cli.stdout.includes("installed from cursor-os 0.0.1"),
+ );
+});
+
// 9. CLI entry point coverage.
console.log("\nCLI:");
withTempDir((dir) => {
@@ -254,6 +321,15 @@ withTempDir((dir) => {
const initDefault = runCli(["init", "--target", dir]);
check("CLI init --target exits 0", initDefault.status === 0);
check("CLI init --target creates AGENTS.md", existsSync(join(dir, "AGENTS.md")));
+ check(
+ "CLI init prints a post-install check with placeholder count",
+ initDefault.stdout.includes("Post-install check:") &&
+ initDefault.stdout.includes("await localization"),
+ );
+ check(
+ "CLI init points at the localization prompt",
+ initDefault.stdout.includes("prompts/localize-cursor-os.md"),
+ );
const doctorInstalled = runCli(["doctor", "--target", dir]);
check("CLI doctor installed dir exits 0", doctorInstalled.status === 0);
@@ -268,9 +344,11 @@ withTempDir((dir) => {
});
withTempDir((dir) => {
+ // Flags without a command must fail instead of silently defaulting to init.
const directDryRun = runCli(["--dry-run"], { cwd: dir });
- check("CLI direct --dry-run exits 0", directDryRun.status === 0);
- check("CLI direct --dry-run writes nothing", listAll(dir).length === 0);
+ check("CLI --dry-run without command exits non-zero", directDryRun.status === 1);
+ check("CLI --dry-run without command prints error", directDryRun.stderr.includes("missing command"));
+ check("CLI --dry-run without command writes nothing", listAll(dir).length === 0);
});
withTempDir((dir) => {
@@ -280,9 +358,19 @@ withTempDir((dir) => {
});
withTempDir((dir) => {
+ // Bare invocation is read-only: prints help, writes nothing.
+ const bare = runCli([], { cwd: dir });
+ check("CLI bare invocation exits 0", bare.status === 0);
+ check("CLI bare invocation prints help", bare.stdout.includes("Usage:") && bare.stdout.includes("doctor"));
+ check("CLI bare invocation writes nothing", listAll(dir).length === 0);
+});
+
+withTempDir((dir) => {
+ // A bare path without a command must fail instead of installing.
const legacyPath = runCli([dir]);
- check("CLI bare path target exits 0 for backwards compatibility", legacyPath.status === 0);
- check("CLI bare path target creates AGENTS.md", existsSync(join(dir, "AGENTS.md")));
+ check("CLI bare path without command exits non-zero", legacyPath.status === 1);
+ check("CLI bare path without command prints error", legacyPath.stderr.includes("missing command"));
+ check("CLI bare path without command writes nothing", listAll(dir).length === 0);
});
withTempDir((dir) => {
@@ -292,19 +380,18 @@ withTempDir((dir) => {
});
withTempDir((dir) => {
- // Bare relative name with no slash must be treated as a target, not an error
- const bareRelative = runCli(["my-project"], { cwd: dir });
- check("CLI bare relative name treated as target, exits 0", bareRelative.status === 0);
- check("CLI bare relative name creates AGENTS.md inside it", existsSync(join(dir, "my-project", "AGENTS.md")));
+ // Bare relative name still resolves as a target when init is explicit.
+ const bareRelative = runCli(["init", "my-project"], { cwd: dir });
+ check("CLI init with bare relative name exits 0", bareRelative.status === 0);
+ check("CLI init with bare relative name creates AGENTS.md inside it", existsSync(join(dir, "my-project", "AGENTS.md")));
});
withTempDir((dir) => {
// Options before subcommand must be allowed: --target DIR doctor
- const targetBeforeCmd = runCli(["--target", dir, "doctor"]);
install({ target: dir });
- const targetBeforeCmd2 = runCli(["--target", dir, "doctor"]);
- check("CLI --target before subcommand is accepted", targetBeforeCmd2.status === 0);
- check("CLI --target before subcommand runs doctor", targetBeforeCmd2.stdout.includes("doctor"));
+ const targetBeforeCmd = runCli(["--target", dir, "doctor"]);
+ check("CLI --target before subcommand is accepted", targetBeforeCmd.status === 0);
+ check("CLI --target before subcommand runs doctor", targetBeforeCmd.stdout.includes("doctor"));
});
withTempDir((dir) => {
diff --git a/template/AGENTS.md b/template/AGENTS.md
index c47f044..63d1820 100644
--- a/template/AGENTS.md
+++ b/template/AGENTS.md
@@ -2,7 +2,7 @@
This file is the engineering contract for this repository. Cursor reads it automatically; other tools can use it as plain markdown project guidance. Keep it accurate as the project's source of truth for *how* work gets done here.
-> After installing Cursor OS, run the localization prompt (`prompts/localize-cursor-os.md`) so the sections below describe *this* project specifically. Replace the TODO markers with real details.
+> After installing Cursor OS, run the localization prompt (`prompts/localize-cursor-os.md`) so the sections below describe *this* project specifically. Replace the placeholder markers with real details, then delete this note.
## Project context
diff --git a/template/docs/repo-memory.md b/template/docs/repo-memory.md
index 2fb1ab6..bdf999a 100644
--- a/template/docs/repo-memory.md
+++ b/template/docs/repo-memory.md
@@ -2,7 +2,7 @@
Durable facts about this repository that an agent should know before working in it. Keep it short, current, and true — stale memory is worse than none. Update it whenever a fact here changes or you learn something the next session would need.
-> Most of this is filled in by the localization prompt (`prompts/localize-cursor-os.md`). Replace TODO markers with real details; delete sections that don't apply. Never invent facts — leave a TODO if unsure.
+> Most of this is filled in by the localization prompt (`prompts/localize-cursor-os.md`). Replace placeholder markers with real details; delete sections that don't apply. Never invent facts — leave a placeholder marker if unsure. Delete this note once localization is done.
## What this project is
diff --git a/template/prompts/localize-cursor-os.md b/template/prompts/localize-cursor-os.md
index 8b6b17d..30dab3d 100644
--- a/template/prompts/localize-cursor-os.md
+++ b/template/prompts/localize-cursor-os.md
@@ -34,9 +34,9 @@ Report any other file you believe should change, but do not change it.
2. **Extract the real commands.** Find the actual install / dev / test / lint / typecheck / build commands from scripts and CI. Use these verbatim — do not assume conventional names.
-3. **Fill `docs/repo-memory.md`.** Replace every TODO you can verify: what the project is, stack, organization, commands, conventions, and constraints. Leave TODOs only where the repo genuinely doesn't answer the question.
+3. **Fill `docs/repo-memory.md`.** Replace every TODO you can verify: what the project is, stack, organization, commands, conventions, and constraints. Leave TODOs only where the repo genuinely doesn't answer the question. When done, delete the install-time blockquote note near the top of the file.
-4. **Fill `AGENTS.md` project-context section.** Keep it to a few true sentences; point to `docs/architecture.md` for detail rather than duplicating it.
+4. **Fill `AGENTS.md` project-context section.** Keep it to a few true sentences; point to `docs/architecture.md` for detail rather than duplicating it. When done, delete the install-time blockquote note near the top of the file.
5. **Create `docs/architecture.md`** if the project is non-trivial: the main modules/services, how data flows, key boundaries, and important patterns — all grounded in the code. Skip it for a tiny repo and say why.