@cstart/coldstart 2.0.0
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 +236 -0
- package/dist/cache/disk-cache.d.ts +14 -0
- package/dist/cache/disk-cache.d.ts.map +1 -0
- package/dist/cache/disk-cache.js +371 -0
- package/dist/cache/disk-cache.js.map +1 -0
- package/dist/cli.d.ts +18 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +206 -0
- package/dist/cli.js.map +1 -0
- package/dist/constants.d.ts +8 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +111 -0
- package/dist/constants.js.map +1 -0
- package/dist/daemon-lock.d.ts +63 -0
- package/dist/daemon-lock.d.ts.map +1 -0
- package/dist/daemon-lock.js +218 -0
- package/dist/daemon-lock.js.map +1 -0
- package/dist/daemon-log.d.ts +26 -0
- package/dist/daemon-log.d.ts.map +1 -0
- package/dist/daemon-log.js +110 -0
- package/dist/daemon-log.js.map +1 -0
- package/dist/index-manager.d.ts +103 -0
- package/dist/index-manager.d.ts.map +1 -0
- package/dist/index-manager.js +361 -0
- package/dist/index-manager.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +724 -0
- package/dist/index.js.map +1 -0
- package/dist/indexer/content-tokens.d.ts +74 -0
- package/dist/indexer/content-tokens.d.ts.map +1 -0
- package/dist/indexer/content-tokens.js +406 -0
- package/dist/indexer/content-tokens.js.map +1 -0
- package/dist/indexer/cpp-include-roots.d.ts +7 -0
- package/dist/indexer/cpp-include-roots.d.ts.map +1 -0
- package/dist/indexer/cpp-include-roots.js +166 -0
- package/dist/indexer/cpp-include-roots.js.map +1 -0
- package/dist/indexer/csharp-synthetic.d.ts +9 -0
- package/dist/indexer/csharp-synthetic.d.ts.map +1 -0
- package/dist/indexer/csharp-synthetic.js +51 -0
- package/dist/indexer/csharp-synthetic.js.map +1 -0
- package/dist/indexer/django-synthetic.d.ts +12 -0
- package/dist/indexer/django-synthetic.d.ts.map +1 -0
- package/dist/indexer/django-synthetic.js +49 -0
- package/dist/indexer/django-synthetic.js.map +1 -0
- package/dist/indexer/extractors/angularjs.d.ts +16 -0
- package/dist/indexer/extractors/angularjs.d.ts.map +1 -0
- package/dist/indexer/extractors/angularjs.js +49 -0
- package/dist/indexer/extractors/angularjs.js.map +1 -0
- package/dist/indexer/extractors/cpp.d.ts +9 -0
- package/dist/indexer/extractors/cpp.d.ts.map +1 -0
- package/dist/indexer/extractors/cpp.js +254 -0
- package/dist/indexer/extractors/cpp.js.map +1 -0
- package/dist/indexer/extractors/csharp.d.ts +19 -0
- package/dist/indexer/extractors/csharp.d.ts.map +1 -0
- package/dist/indexer/extractors/csharp.js +327 -0
- package/dist/indexer/extractors/csharp.js.map +1 -0
- package/dist/indexer/extractors/env.d.ts +9 -0
- package/dist/indexer/extractors/env.d.ts.map +1 -0
- package/dist/indexer/extractors/env.js +43 -0
- package/dist/indexer/extractors/env.js.map +1 -0
- package/dist/indexer/extractors/go.d.ts +9 -0
- package/dist/indexer/extractors/go.d.ts.map +1 -0
- package/dist/indexer/extractors/go.js +227 -0
- package/dist/indexer/extractors/go.js.map +1 -0
- package/dist/indexer/extractors/graphql.d.ts +22 -0
- package/dist/indexer/extractors/graphql.d.ts.map +1 -0
- package/dist/indexer/extractors/graphql.js +88 -0
- package/dist/indexer/extractors/graphql.js.map +1 -0
- package/dist/indexer/extractors/groovy.d.ts +9 -0
- package/dist/indexer/extractors/groovy.d.ts.map +1 -0
- package/dist/indexer/extractors/groovy.js +356 -0
- package/dist/indexer/extractors/groovy.js.map +1 -0
- package/dist/indexer/extractors/java.d.ts +10 -0
- package/dist/indexer/extractors/java.d.ts.map +1 -0
- package/dist/indexer/extractors/java.js +586 -0
- package/dist/indexer/extractors/java.js.map +1 -0
- package/dist/indexer/extractors/kotlin.d.ts +10 -0
- package/dist/indexer/extractors/kotlin.d.ts.map +1 -0
- package/dist/indexer/extractors/kotlin.js +398 -0
- package/dist/indexer/extractors/kotlin.js.map +1 -0
- package/dist/indexer/extractors/node-helpers.d.ts +13 -0
- package/dist/indexer/extractors/node-helpers.d.ts.map +1 -0
- package/dist/indexer/extractors/node-helpers.js +17 -0
- package/dist/indexer/extractors/node-helpers.js.map +1 -0
- package/dist/indexer/extractors/parser-factory.d.ts +4 -0
- package/dist/indexer/extractors/parser-factory.d.ts.map +1 -0
- package/dist/indexer/extractors/parser-factory.js +28 -0
- package/dist/indexer/extractors/parser-factory.js.map +1 -0
- package/dist/indexer/extractors/php.d.ts +17 -0
- package/dist/indexer/extractors/php.d.ts.map +1 -0
- package/dist/indexer/extractors/php.js +363 -0
- package/dist/indexer/extractors/php.js.map +1 -0
- package/dist/indexer/extractors/python.d.ts +14 -0
- package/dist/indexer/extractors/python.d.ts.map +1 -0
- package/dist/indexer/extractors/python.js +449 -0
- package/dist/indexer/extractors/python.js.map +1 -0
- package/dist/indexer/extractors/ruby.d.ts +10 -0
- package/dist/indexer/extractors/ruby.d.ts.map +1 -0
- package/dist/indexer/extractors/ruby.js +996 -0
- package/dist/indexer/extractors/ruby.js.map +1 -0
- package/dist/indexer/extractors/rust.d.ts +9 -0
- package/dist/indexer/extractors/rust.d.ts.map +1 -0
- package/dist/indexer/extractors/rust.js +322 -0
- package/dist/indexer/extractors/rust.js.map +1 -0
- package/dist/indexer/extractors/toml.d.ts +9 -0
- package/dist/indexer/extractors/toml.d.ts.map +1 -0
- package/dist/indexer/extractors/toml.js +84 -0
- package/dist/indexer/extractors/toml.js.map +1 -0
- package/dist/indexer/extractors/xml.d.ts +9 -0
- package/dist/indexer/extractors/xml.d.ts.map +1 -0
- package/dist/indexer/extractors/xml.js +163 -0
- package/dist/indexer/extractors/xml.js.map +1 -0
- package/dist/indexer/extractors/yaml.d.ts +9 -0
- package/dist/indexer/extractors/yaml.d.ts.map +1 -0
- package/dist/indexer/extractors/yaml.js +117 -0
- package/dist/indexer/extractors/yaml.js.map +1 -0
- package/dist/indexer/git.d.ts +19 -0
- package/dist/indexer/git.d.ts.map +1 -0
- package/dist/indexer/git.js +55 -0
- package/dist/indexer/git.js.map +1 -0
- package/dist/indexer/graph.d.ts +7 -0
- package/dist/indexer/graph.d.ts.map +1 -0
- package/dist/indexer/graph.js +20 -0
- package/dist/indexer/graph.js.map +1 -0
- package/dist/indexer/indexed-file.d.ts +15 -0
- package/dist/indexer/indexed-file.d.ts.map +1 -0
- package/dist/indexer/indexed-file.js +50 -0
- package/dist/indexer/indexed-file.js.map +1 -0
- package/dist/indexer/invariants.d.ts +15 -0
- package/dist/indexer/invariants.d.ts.map +1 -0
- package/dist/indexer/invariants.js +96 -0
- package/dist/indexer/invariants.js.map +1 -0
- package/dist/indexer/laravel-synthetic.d.ts +10 -0
- package/dist/indexer/laravel-synthetic.d.ts.map +1 -0
- package/dist/indexer/laravel-synthetic.js +78 -0
- package/dist/indexer/laravel-synthetic.js.map +1 -0
- package/dist/indexer/parser.d.ts +4 -0
- package/dist/indexer/parser.d.ts.map +1 -0
- package/dist/indexer/parser.js +499 -0
- package/dist/indexer/parser.js.map +1 -0
- package/dist/indexer/patch.d.ts +3 -0
- package/dist/indexer/patch.d.ts.map +1 -0
- package/dist/indexer/patch.js +291 -0
- package/dist/indexer/patch.js.map +1 -0
- package/dist/indexer/rails-synthetic.d.ts +11 -0
- package/dist/indexer/rails-synthetic.d.ts.map +1 -0
- package/dist/indexer/rails-synthetic.js +80 -0
- package/dist/indexer/rails-synthetic.js.map +1 -0
- package/dist/indexer/reconcile.d.ts +9 -0
- package/dist/indexer/reconcile.d.ts.map +1 -0
- package/dist/indexer/reconcile.js +96 -0
- package/dist/indexer/reconcile.js.map +1 -0
- package/dist/indexer/resolvers/cpp.d.ts +15 -0
- package/dist/indexer/resolvers/cpp.d.ts.map +1 -0
- package/dist/indexer/resolvers/cpp.js +35 -0
- package/dist/indexer/resolvers/cpp.js.map +1 -0
- package/dist/indexer/resolvers/csharp.d.ts +2 -0
- package/dist/indexer/resolvers/csharp.d.ts.map +1 -0
- package/dist/indexer/resolvers/csharp.js +89 -0
- package/dist/indexer/resolvers/csharp.js.map +1 -0
- package/dist/indexer/resolvers/generic.d.ts +10 -0
- package/dist/indexer/resolvers/generic.d.ts.map +1 -0
- package/dist/indexer/resolvers/generic.js +35 -0
- package/dist/indexer/resolvers/generic.js.map +1 -0
- package/dist/indexer/resolvers/go.d.ts +2 -0
- package/dist/indexer/resolvers/go.d.ts.map +1 -0
- package/dist/indexer/resolvers/go.js +171 -0
- package/dist/indexer/resolvers/go.js.map +1 -0
- package/dist/indexer/resolvers/index.d.ts +26 -0
- package/dist/indexer/resolvers/index.d.ts.map +1 -0
- package/dist/indexer/resolvers/index.js +301 -0
- package/dist/indexer/resolvers/index.js.map +1 -0
- package/dist/indexer/resolvers/java.d.ts +17 -0
- package/dist/indexer/resolvers/java.d.ts.map +1 -0
- package/dist/indexer/resolvers/java.js +104 -0
- package/dist/indexer/resolvers/java.js.map +1 -0
- package/dist/indexer/resolvers/php.d.ts +2 -0
- package/dist/indexer/resolvers/php.d.ts.map +1 -0
- package/dist/indexer/resolvers/php.js +108 -0
- package/dist/indexer/resolvers/php.js.map +1 -0
- package/dist/indexer/resolvers/python.d.ts +17 -0
- package/dist/indexer/resolvers/python.d.ts.map +1 -0
- package/dist/indexer/resolvers/python.js +63 -0
- package/dist/indexer/resolvers/python.js.map +1 -0
- package/dist/indexer/resolvers/ruby.d.ts +13 -0
- package/dist/indexer/resolvers/ruby.d.ts.map +1 -0
- package/dist/indexer/resolvers/ruby.js +199 -0
- package/dist/indexer/resolvers/ruby.js.map +1 -0
- package/dist/indexer/resolvers/rust.d.ts +12 -0
- package/dist/indexer/resolvers/rust.d.ts.map +1 -0
- package/dist/indexer/resolvers/rust.js +61 -0
- package/dist/indexer/resolvers/rust.js.map +1 -0
- package/dist/indexer/resolvers/shared.d.ts +18 -0
- package/dist/indexer/resolvers/shared.d.ts.map +1 -0
- package/dist/indexer/resolvers/shared.js +71 -0
- package/dist/indexer/resolvers/shared.js.map +1 -0
- package/dist/indexer/rust-workspace.d.ts +25 -0
- package/dist/indexer/rust-workspace.d.ts.map +1 -0
- package/dist/indexer/rust-workspace.js +155 -0
- package/dist/indexer/rust-workspace.js.map +1 -0
- package/dist/indexer/symbol-edges.d.ts +19 -0
- package/dist/indexer/symbol-edges.d.ts.map +1 -0
- package/dist/indexer/symbol-edges.js +117 -0
- package/dist/indexer/symbol-edges.js.map +1 -0
- package/dist/indexer/tokenize.d.ts +26 -0
- package/dist/indexer/tokenize.d.ts.map +1 -0
- package/dist/indexer/tokenize.js +200 -0
- package/dist/indexer/tokenize.js.map +1 -0
- package/dist/indexer/ts-parser.d.ts +10 -0
- package/dist/indexer/ts-parser.d.ts.map +1 -0
- package/dist/indexer/ts-parser.js +605 -0
- package/dist/indexer/ts-parser.js.map +1 -0
- package/dist/indexer/walker.d.ts +9 -0
- package/dist/indexer/walker.d.ts.map +1 -0
- package/dist/indexer/walker.js +91 -0
- package/dist/indexer/walker.js.map +1 -0
- package/dist/init.d.ts +111 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +787 -0
- package/dist/init.js.map +1 -0
- package/dist/kb/cli.d.ts +2 -0
- package/dist/kb/cli.d.ts.map +1 -0
- package/dist/kb/cli.js +377 -0
- package/dist/kb/cli.js.map +1 -0
- package/dist/kb/commit.d.ts +6 -0
- package/dist/kb/commit.d.ts.map +1 -0
- package/dist/kb/commit.js +84 -0
- package/dist/kb/commit.js.map +1 -0
- package/dist/kb/fold.d.ts +42 -0
- package/dist/kb/fold.d.ts.map +1 -0
- package/dist/kb/fold.js +285 -0
- package/dist/kb/fold.js.map +1 -0
- package/dist/kb/freshness.d.ts +9 -0
- package/dist/kb/freshness.d.ts.map +1 -0
- package/dist/kb/freshness.js +52 -0
- package/dist/kb/freshness.js.map +1 -0
- package/dist/kb/git.d.ts +4 -0
- package/dist/kb/git.d.ts.map +1 -0
- package/dist/kb/git.js +28 -0
- package/dist/kb/git.js.map +1 -0
- package/dist/kb/ids.d.ts +8 -0
- package/dist/kb/ids.d.ts.map +1 -0
- package/dist/kb/ids.js +42 -0
- package/dist/kb/ids.js.map +1 -0
- package/dist/kb/lint.d.ts +23 -0
- package/dist/kb/lint.d.ts.map +1 -0
- package/dist/kb/lint.js +102 -0
- package/dist/kb/lint.js.map +1 -0
- package/dist/kb/lookup.d.ts +55 -0
- package/dist/kb/lookup.d.ts.map +1 -0
- package/dist/kb/lookup.js +92 -0
- package/dist/kb/lookup.js.map +1 -0
- package/dist/kb/notes-index.d.ts +31 -0
- package/dist/kb/notes-index.d.ts.map +1 -0
- package/dist/kb/notes-index.js +0 -0
- package/dist/kb/notes-index.js.map +1 -0
- package/dist/kb/raw-log.d.ts +34 -0
- package/dist/kb/raw-log.d.ts.map +1 -0
- package/dist/kb/raw-log.js +106 -0
- package/dist/kb/raw-log.js.map +1 -0
- package/dist/kb/render.d.ts +17 -0
- package/dist/kb/render.d.ts.map +1 -0
- package/dist/kb/render.js +101 -0
- package/dist/kb/render.js.map +1 -0
- package/dist/kb/search.d.ts +110 -0
- package/dist/kb/search.d.ts.map +1 -0
- package/dist/kb/search.js +453 -0
- package/dist/kb/search.js.map +1 -0
- package/dist/kb/store.d.ts +35 -0
- package/dist/kb/store.d.ts.map +1 -0
- package/dist/kb/store.js +160 -0
- package/dist/kb/store.js.map +1 -0
- package/dist/kb/types.d.ts +142 -0
- package/dist/kb/types.d.ts.map +1 -0
- package/dist/kb/types.js +13 -0
- package/dist/kb/types.js.map +1 -0
- package/dist/kb/view-template.d.ts +8 -0
- package/dist/kb/view-template.d.ts.map +1 -0
- package/dist/kb/view-template.js +8 -0
- package/dist/kb/view-template.js.map +1 -0
- package/dist/kb/view.d.ts +42 -0
- package/dist/kb/view.d.ts.map +1 -0
- package/dist/kb/view.js +118 -0
- package/dist/kb/view.js.map +1 -0
- package/dist/kb/write.d.ts +72 -0
- package/dist/kb/write.d.ts.map +1 -0
- package/dist/kb/write.js +345 -0
- package/dist/kb/write.js.map +1 -0
- package/dist/keeper-state.d.ts +29 -0
- package/dist/keeper-state.d.ts.map +1 -0
- package/dist/keeper-state.js +89 -0
- package/dist/keeper-state.js.map +1 -0
- package/dist/keeper.d.ts +23 -0
- package/dist/keeper.d.ts.map +1 -0
- package/dist/keeper.js +105 -0
- package/dist/keeper.js.map +1 -0
- package/dist/migrate.d.ts +9 -0
- package/dist/migrate.d.ts.map +1 -0
- package/dist/migrate.js +102 -0
- package/dist/migrate.js.map +1 -0
- package/dist/restart.d.ts +15 -0
- package/dist/restart.d.ts.map +1 -0
- package/dist/restart.js +48 -0
- package/dist/restart.js.map +1 -0
- package/dist/server/find.d.ts +39 -0
- package/dist/server/find.d.ts.map +1 -0
- package/dist/server/find.js +979 -0
- package/dist/server/find.js.map +1 -0
- package/dist/server/mcp.d.ts +117 -0
- package/dist/server/mcp.d.ts.map +1 -0
- package/dist/server/mcp.js +301 -0
- package/dist/server/mcp.js.map +1 -0
- package/dist/server/searcher.d.ts +14 -0
- package/dist/server/searcher.d.ts.map +1 -0
- package/dist/server/searcher.js +146 -0
- package/dist/server/searcher.js.map +1 -0
- package/dist/server/tools.d.ts +17 -0
- package/dist/server/tools.d.ts.map +1 -0
- package/dist/server/tools.js +875 -0
- package/dist/server/tools.js.map +1 -0
- package/dist/status.d.ts +12 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +157 -0
- package/dist/status.js.map +1 -0
- package/dist/types.d.ts +138 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/watcher.d.ts +11 -0
- package/dist/watcher.d.ts.map +1 -0
- package/dist/watcher.js +67 -0
- package/dist/watcher.js.map +1 -0
- package/hooks/canonical-find-key.mjs +68 -0
- package/hooks/codex-find-nudge.mjs +15 -0
- package/hooks/codex-find-preguard.mjs +14 -0
- package/hooks/codex-kb-elicit.mjs +329 -0
- package/hooks/codex-kb-recall.mjs +151 -0
- package/hooks/codex-nudge-handler.mjs +370 -0
- package/hooks/codex-preguard-handler.mjs +75 -0
- package/hooks/codex-run-hook.mjs +132 -0
- package/hooks/coldstart-call.mjs +58 -0
- package/hooks/cursor-find-nudge.mjs +15 -0
- package/hooks/cursor-find-preguard.mjs +14 -0
- package/hooks/cursor-input.mjs +72 -0
- package/hooks/cursor-kb-elicit.mjs +335 -0
- package/hooks/cursor-kb-recall.mjs +138 -0
- package/hooks/cursor-nudge-handler.mjs +33 -0
- package/hooks/cursor-preguard-handler.mjs +39 -0
- package/hooks/cursor-run-hook.mjs +129 -0
- package/hooks/find-nudge.mjs +15 -0
- package/hooks/find-preguard.mjs +14 -0
- package/hooks/kb-elicit.mjs +334 -0
- package/hooks/kb-recall.mjs +151 -0
- package/hooks/nudge-handler.mjs +370 -0
- package/hooks/preguard-handler.mjs +75 -0
- package/hooks/run-hook.mjs +132 -0
- package/package.json +69 -0
- package/templates/coldstart.cli.md +60 -0
- package/templates/coldstart.mcp.md +59 -0
package/dist/init.js
ADDED
|
@@ -0,0 +1,787 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* coldstart init — wire coldstart into a project.
|
|
3
|
+
*
|
|
4
|
+
* Model: a single `coldstart.md` lives at the repo root and carries ALL the
|
|
5
|
+
* agent-facing guidance. Clients pull it in by reference, so future wording
|
|
6
|
+
* changes touch only coldstart.md, never the client's own rules file.
|
|
7
|
+
*
|
|
8
|
+
* init asks two things (or takes `--experience` / `--client` flags):
|
|
9
|
+
* 1. EXPERIENCE — how the agent invokes coldstart:
|
|
10
|
+
* - `cli` (recommended) → runs `coldstart find` / `coldstart gs` (shell).
|
|
11
|
+
* - `mcp` → calls the `find` / `gs` MCP tools (no shell).
|
|
12
|
+
* This selects the coldstart.md flavor and the hook matcher surface.
|
|
13
|
+
* 2. CLIENT — which tool to wire (never auto-detected; the user always picks):
|
|
14
|
+
* - Claude Code → CLAUDE.md imports `@coldstart.md`; find/gs hooks in
|
|
15
|
+
* `.claude/settings.json`. (MCP experience also writes `.mcp.json`.)
|
|
16
|
+
* - Cursor → `.cursor/rules/coldstart.mdc` references coldstart.md;
|
|
17
|
+
* Cursor-specific navigation + notebook hooks in `.cursor/hooks.json`.
|
|
18
|
+
* (MCP) also writes `.cursor/mcp.json`.
|
|
19
|
+
* - Codex → AGENTS.md points at coldstart.md; Codex-specific
|
|
20
|
+
* navigation + notebook hooks in `.codex/hooks.json`. (MCP) writes
|
|
21
|
+
* `[mcp_servers.coldstart]` into `.codex/config.toml`.
|
|
22
|
+
* - Other → write coldstart.md only; print wiring directions.
|
|
23
|
+
*
|
|
24
|
+
* Claude, Codex, and Cursor receive separate hook entrypoints. They share the
|
|
25
|
+
* protocol-neutral detector core; only input adaptation, the transcript walk,
|
|
26
|
+
* and the output envelope differ per host (Cursor capture parses its own JSONL).
|
|
27
|
+
*/
|
|
28
|
+
import * as fs from 'node:fs';
|
|
29
|
+
import * as path from 'node:path';
|
|
30
|
+
import { execFileSync } from 'node:child_process';
|
|
31
|
+
import { createInterface } from 'node:readline/promises';
|
|
32
|
+
import { fileURLToPath } from 'node:url';
|
|
33
|
+
import { ensureKeeper } from './keeper.js';
|
|
34
|
+
import { initSkeleton, logMetric } from './kb/store.js';
|
|
35
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
36
|
+
const DIVIDER = '─'.repeat(60);
|
|
37
|
+
const IMPORT_LINE = '@coldstart.md';
|
|
38
|
+
function out(msg) {
|
|
39
|
+
process.stderr.write(msg + '\n');
|
|
40
|
+
}
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// coldstart.md content — two checked-in flavors under templates/, one written
|
|
43
|
+
// per experience. Editing docs = editing plain markdown (templates/coldstart.
|
|
44
|
+
// {cli,mcp}.md), not escaped TS template strings. templates/ ships in the npm
|
|
45
|
+
// package (package.json "files") beside dist/, so it resolves from the running
|
|
46
|
+
// install. Trade-off: the ~shared prose lives in both files — keep them in sync
|
|
47
|
+
// when you edit shared guidance (only the find/gs phrasing + notebook commands
|
|
48
|
+
// genuinely differ between flavors).
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
/** Absolute path to a flavored coldstart.md template. templates/ sits beside
|
|
51
|
+
* dist/ in both the checkout and the published package (dist/init.js → ..). */
|
|
52
|
+
function coldstartMdTemplatePath(mode) {
|
|
53
|
+
return path.resolve(path.dirname(path.dirname(__filename)), 'templates', `coldstart.${mode}.md`);
|
|
54
|
+
}
|
|
55
|
+
/** The coldstart.md body for a flavor, read from its checked-in template. */
|
|
56
|
+
export function coldstartMd(mode) {
|
|
57
|
+
return fs.readFileSync(coldstartMdTemplatePath(mode), 'utf8');
|
|
58
|
+
}
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Stable install resolution + MCP entry (printed for no-shell / other clients)
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
/**
|
|
63
|
+
* Package root of the running coldstart install — the dir holding `dist/`,
|
|
64
|
+
* `hooks/`, `templates/`, and `package.json`. Derived from THIS module's own
|
|
65
|
+
* location, so it resolves identically whether we run compiled (`dist/init.js`)
|
|
66
|
+
* or from source (`src/init.ts`); no HOME lookup, no copy.
|
|
67
|
+
*
|
|
68
|
+
* We point hooks and MCP config straight at this live install. `npx` is no
|
|
69
|
+
* longer a supported flow (install is `npm i -g @cstart/coldstart` then `coldstart
|
|
70
|
+
* init`), so the running path is always stable — which makes the old
|
|
71
|
+
* version-pinned copy into `~/.coldstart/versions/<v>/` pure liability:
|
|
72
|
+
* - `npm update -g @cstart/coldstart` is now picked up automatically (no stale snapshot).
|
|
73
|
+
* - `npm uninstall -g @cstart/coldstart` actually disables the wired hooks, instead of
|
|
74
|
+
* a hidden copy that keeps executing on every tool call after removal.
|
|
75
|
+
*/
|
|
76
|
+
function installRoot() {
|
|
77
|
+
const root = path.dirname(path.dirname(__filename)); // <install>/dist/init.js → <install>
|
|
78
|
+
if (!fs.existsSync(path.join(root, 'hooks'))) {
|
|
79
|
+
throw new Error(`coldstart install at ${root} is missing hooks/ — reinstall coldstart`);
|
|
80
|
+
}
|
|
81
|
+
return root;
|
|
82
|
+
}
|
|
83
|
+
function mcpServerEntry(cwd) {
|
|
84
|
+
const entryPath = path.join(installRoot(), 'dist', 'index.js');
|
|
85
|
+
return { command: 'node', args: [entryPath, '--root', cwd] };
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Absolute path to the shipped hooks dir (sibling of `dist/`) in the running
|
|
89
|
+
* install. The path written into settings.json / hooks.json points at the live
|
|
90
|
+
* install, so uninstalling coldstart disables the hooks (see installRoot).
|
|
91
|
+
*/
|
|
92
|
+
function resolveHooksDir() {
|
|
93
|
+
return path.join(installRoot(), 'hooks');
|
|
94
|
+
}
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
// Notebook (kb) setup — skeleton, git wiring, and the Claude recall/capture
|
|
97
|
+
// hooks. Shared by `coldstart init` (always) and the `coldstart kb init` alias.
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// The notebook recall (UserPromptSubmit) + capture (Stop/SubagentStop) hook
|
|
100
|
+
// entry files, in the shipped hooks/ dir. Distinct from the find/gs hooks, so
|
|
101
|
+
// they merge into settings.json independently.
|
|
102
|
+
const KB_HOOK_RECALL = 'kb-recall.mjs';
|
|
103
|
+
const KB_HOOK_ELICIT = 'kb-elicit.mjs';
|
|
104
|
+
/** True if a hook-array entry is one of OUR kb hooks (by filename) — so re-init
|
|
105
|
+
* strips + refreshes instead of duplicating. */
|
|
106
|
+
function isKbHookEntry(entry) {
|
|
107
|
+
const hooks = entry?.hooks;
|
|
108
|
+
if (!Array.isArray(hooks))
|
|
109
|
+
return false;
|
|
110
|
+
return hooks.some((h) => {
|
|
111
|
+
const cmd = h?.command;
|
|
112
|
+
return typeof cmd === 'string' && (cmd.includes(KB_HOOK_RECALL) || cmd.includes(KB_HOOK_ELICIT));
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Merge the notebook recall/capture hooks into `.claude/settings.json`.
|
|
117
|
+
* Idempotent (strips prior kb entries, re-adds), preserves the find/gs hooks
|
|
118
|
+
* and any foreign entries, fail-safe on invalid JSON. Points at the same
|
|
119
|
+
* version-pinned stable hooks dir the find/gs hooks use (survives npm update).
|
|
120
|
+
*/
|
|
121
|
+
export function wireClaudeKbHooks(cwd) {
|
|
122
|
+
let hooksDir;
|
|
123
|
+
try {
|
|
124
|
+
hooksDir = resolveHooksDir();
|
|
125
|
+
}
|
|
126
|
+
catch (e) {
|
|
127
|
+
return { error: `could not resolve a stable install path (${e})` };
|
|
128
|
+
}
|
|
129
|
+
const dir = path.join(cwd, '.claude');
|
|
130
|
+
const filePath = path.join(dir, 'settings.json');
|
|
131
|
+
let settings = {};
|
|
132
|
+
const existed = fs.existsSync(filePath);
|
|
133
|
+
if (existed) {
|
|
134
|
+
try {
|
|
135
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
136
|
+
if (parsed && typeof parsed === 'object')
|
|
137
|
+
settings = parsed;
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return { error: `${filePath} is not valid JSON — left untouched; wire the kb hooks manually` };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const hooksCfg = (settings.hooks && typeof settings.hooks === 'object' ? settings.hooks : {});
|
|
144
|
+
const stripOurs = (arr) => (Array.isArray(arr) ? arr : []).filter((e) => !isKbHookEntry(e));
|
|
145
|
+
const entry = (file) => ({ hooks: [{ type: 'command', command: `node ${path.join(hooksDir, file)}` }] });
|
|
146
|
+
hooksCfg.UserPromptSubmit = [...stripOurs(hooksCfg.UserPromptSubmit), entry(KB_HOOK_RECALL)];
|
|
147
|
+
hooksCfg.Stop = [...stripOurs(hooksCfg.Stop), entry(KB_HOOK_ELICIT)];
|
|
148
|
+
hooksCfg.SubagentStop = [...stripOurs(hooksCfg.SubagentStop), entry(KB_HOOK_ELICIT)];
|
|
149
|
+
settings.hooks = hooksCfg;
|
|
150
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
151
|
+
fs.writeFileSync(filePath, JSON.stringify(settings, null, 2) + '\n');
|
|
152
|
+
return existed ? 'updated' : 'created';
|
|
153
|
+
}
|
|
154
|
+
/** Is any `.raw` note file already git-tracked? If so the repo is (or was)
|
|
155
|
+
* sharing its notebook — adding an ignore line would create a confusing
|
|
156
|
+
* tracked-but-ignored divergence, so we leave it shared. */
|
|
157
|
+
function notebookRawTracked(cwd) {
|
|
158
|
+
try {
|
|
159
|
+
const listed = execFileSync('git', ['ls-files', '.coldstart/notebook/.raw'], {
|
|
160
|
+
cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
|
|
161
|
+
});
|
|
162
|
+
return listed.trim().length > 0;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return false; // not a git repo, or git unavailable — treat as untracked
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const NOTEBOOK_IGNORE_LINE = '.coldstart/';
|
|
169
|
+
const hasNotebookIgnore = (text) => text.split('\n').some((l) => { const t = l.trim(); return t === '.coldstart/' || t === '.coldstart'; });
|
|
170
|
+
/** Privacy default: add `.coldstart/` to the repo's root .gitignore so the
|
|
171
|
+
* notebook stays local. Skipped when `.raw` is already tracked (don't flip an
|
|
172
|
+
* existing shared repo). */
|
|
173
|
+
function addNotebookGitignore(cwd) {
|
|
174
|
+
if (notebookRawTracked(cwd))
|
|
175
|
+
return 'kept-shared';
|
|
176
|
+
const giPath = path.join(cwd, '.gitignore');
|
|
177
|
+
let gi = '';
|
|
178
|
+
try {
|
|
179
|
+
gi = fs.existsSync(giPath) ? fs.readFileSync(giPath, 'utf8') : '';
|
|
180
|
+
}
|
|
181
|
+
catch { /* create below */ }
|
|
182
|
+
if (hasNotebookIgnore(gi))
|
|
183
|
+
return 'present';
|
|
184
|
+
fs.appendFileSync(giPath, (gi && !gi.endsWith('\n') ? '\n' : '') + NOTEBOOK_IGNORE_LINE + '\n');
|
|
185
|
+
return 'added';
|
|
186
|
+
}
|
|
187
|
+
/** Opt-in to share: drop the `.coldstart/` ignore line if we (or the user) added
|
|
188
|
+
* it, so `.raw` becomes committable. The inner notebook .gitignore still keeps
|
|
189
|
+
* notes/ + .metrics/ out. */
|
|
190
|
+
function removeNotebookGitignore(cwd) {
|
|
191
|
+
const giPath = path.join(cwd, '.gitignore');
|
|
192
|
+
if (!fs.existsSync(giPath))
|
|
193
|
+
return 'absent';
|
|
194
|
+
const lines = fs.readFileSync(giPath, 'utf8').split('\n');
|
|
195
|
+
const kept = lines.filter((l) => { const t = l.trim(); return t !== '.coldstart/' && t !== '.coldstart'; });
|
|
196
|
+
if (kept.length === lines.length)
|
|
197
|
+
return 'absent';
|
|
198
|
+
fs.writeFileSync(giPath, kept.join('\n'));
|
|
199
|
+
return 'removed';
|
|
200
|
+
}
|
|
201
|
+
/** merge=union on the append-only .raw logs — concurrent appends conflict at
|
|
202
|
+
* EOF under naive merge; union keeps both sides and the ts-sorted fold makes
|
|
203
|
+
* interleave order irrelevant. */
|
|
204
|
+
function ensureGitattributes(cwd) {
|
|
205
|
+
const gaPath = path.join(cwd, '.gitattributes');
|
|
206
|
+
const line = '.coldstart/notebook/.raw/*.jsonl merge=union';
|
|
207
|
+
let ga = '';
|
|
208
|
+
try {
|
|
209
|
+
ga = fs.existsSync(gaPath) ? fs.readFileSync(gaPath, 'utf8') : '';
|
|
210
|
+
}
|
|
211
|
+
catch { /* create below */ }
|
|
212
|
+
if (ga.includes(line))
|
|
213
|
+
return;
|
|
214
|
+
try {
|
|
215
|
+
fs.appendFileSync(gaPath, (ga && !ga.endsWith('\n') ? '\n' : '') + line + '\n');
|
|
216
|
+
}
|
|
217
|
+
catch (e) {
|
|
218
|
+
out(` (could not write .gitattributes: ${e instanceof Error ? e.message : e})`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Notebook file setup — client-agnostic. Creates the skeleton, sets the
|
|
223
|
+
* merge=union attribute, and applies the storage choice: private by default
|
|
224
|
+
* (gitignore `.coldstart/`), or committable when `commit` is set. Does NOT wire
|
|
225
|
+
* hooks — those are Claude-specific (see wireClaudeKbHooks).
|
|
226
|
+
*/
|
|
227
|
+
export function setupNotebook(cwd, commit) {
|
|
228
|
+
initSkeleton(cwd);
|
|
229
|
+
ensureGitattributes(cwd);
|
|
230
|
+
if (commit) {
|
|
231
|
+
const r = removeNotebookGitignore(cwd);
|
|
232
|
+
out(` notebook — shared: .raw + okf.yaml committable, publish with \`coldstart kb commit\`${r === 'removed' ? ' (removed prior ignore rule)' : ''}`);
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
const r = addNotebookGitignore(cwd);
|
|
236
|
+
const note = r === 'kept-shared' ? 'left shared (.raw already git-tracked)'
|
|
237
|
+
: r === 'added' ? 'private: .coldstart/ gitignored (share later with `coldstart init --commit-notebook`)'
|
|
238
|
+
: 'private: .coldstart/ already gitignored';
|
|
239
|
+
out(` notebook — ${note}`);
|
|
240
|
+
}
|
|
241
|
+
logMetric(cwd, 'capture', { event: 'init' });
|
|
242
|
+
}
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
// Writers
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
export function writeColdstartMd(cwd, mode) {
|
|
247
|
+
const filePath = path.join(cwd, 'coldstart.md');
|
|
248
|
+
const existed = fs.existsSync(filePath);
|
|
249
|
+
fs.writeFileSync(filePath, coldstartMd(mode));
|
|
250
|
+
return existed ? 'updated' : 'created';
|
|
251
|
+
}
|
|
252
|
+
/** Ensure CLAUDE.md exists and imports coldstart.md via `@coldstart.md`. */
|
|
253
|
+
export function wireClaudeImport(cwd) {
|
|
254
|
+
const filePath = path.join(cwd, 'CLAUDE.md');
|
|
255
|
+
if (!fs.existsSync(filePath)) {
|
|
256
|
+
fs.writeFileSync(filePath, `# Project guidance\n\n${IMPORT_LINE}\n`);
|
|
257
|
+
return 'created';
|
|
258
|
+
}
|
|
259
|
+
const existing = fs.readFileSync(filePath, 'utf8');
|
|
260
|
+
if (existing.includes(IMPORT_LINE))
|
|
261
|
+
return 'present';
|
|
262
|
+
const sep = existing.endsWith('\n') ? '' : '\n';
|
|
263
|
+
fs.appendFileSync(filePath, `${sep}\n${IMPORT_LINE}\n`);
|
|
264
|
+
return 'added';
|
|
265
|
+
}
|
|
266
|
+
// Hook entry files (in the shipped hooks/ dir). The PostToolUse nudge fires
|
|
267
|
+
// search-behaviour advice; the PreToolUse guard denies an exact find re-run.
|
|
268
|
+
const HOOK_PRE = 'find-preguard.mjs';
|
|
269
|
+
const HOOK_POST = 'find-nudge.mjs';
|
|
270
|
+
const CODEX_HOOK_PRE = 'codex-find-preguard.mjs';
|
|
271
|
+
const CODEX_HOOK_POST = 'codex-find-nudge.mjs';
|
|
272
|
+
const CODEX_KB_HOOK_RECALL = 'codex-kb-recall.mjs';
|
|
273
|
+
const CODEX_KB_HOOK_ELICIT = 'codex-kb-elicit.mjs';
|
|
274
|
+
const CURSOR_HOOK_PRE = 'cursor-find-preguard.mjs';
|
|
275
|
+
const CURSOR_HOOK_POST = 'cursor-find-nudge.mjs';
|
|
276
|
+
const CURSOR_KB_HOOK_RECALL = 'cursor-kb-recall.mjs';
|
|
277
|
+
const CURSOR_KB_HOOK_ELICIT = 'cursor-kb-elicit.mjs';
|
|
278
|
+
// PreToolUse matcher — surface-agnostic: fires for the CLI `coldstart find`
|
|
279
|
+
// (Bash) AND the `mcp__coldstart__find` tool. A plain regex alternation, so it
|
|
280
|
+
// works in both Claude's and Codex's matcher engine unchanged.
|
|
281
|
+
const PRE_MATCHER = 'Bash|mcp__coldstart__find';
|
|
282
|
+
/** True if a hook-array entry is one WE wrote (by entry filename), so re-running
|
|
283
|
+
* init can strip + refresh it instead of duplicating it. */
|
|
284
|
+
function isColdstartHookEntry(entry) {
|
|
285
|
+
const hooks = entry?.hooks;
|
|
286
|
+
if (!Array.isArray(hooks))
|
|
287
|
+
return false;
|
|
288
|
+
return hooks.some((h) => {
|
|
289
|
+
const cmd = h?.command;
|
|
290
|
+
return typeof cmd === 'string' && (cmd.includes(HOOK_PRE) || cmd.includes(HOOK_POST));
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
function isCodexHookEntry(entry) {
|
|
294
|
+
const hooks = entry?.hooks;
|
|
295
|
+
if (!Array.isArray(hooks))
|
|
296
|
+
return false;
|
|
297
|
+
const owned = [HOOK_PRE, HOOK_POST, KB_HOOK_RECALL, KB_HOOK_ELICIT,
|
|
298
|
+
CODEX_HOOK_PRE, CODEX_HOOK_POST, CODEX_KB_HOOK_RECALL, CODEX_KB_HOOK_ELICIT];
|
|
299
|
+
return hooks.some((h) => {
|
|
300
|
+
const cmd = h?.command;
|
|
301
|
+
return typeof cmd === 'string' && owned.some((file) => cmd.includes(file));
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Build the coldstart hook entries (find-dedup guard + behavioral nudge).
|
|
306
|
+
* `postMatcher` differs by engine: Claude uses `*` for match-all, Codex uses the
|
|
307
|
+
* regex `.*`. Everything else is identical — the shipped handlers are the same.
|
|
308
|
+
*/
|
|
309
|
+
function coldstartHooks(hooksDir, postMatcher) {
|
|
310
|
+
const preCmd = `node ${path.join(hooksDir, HOOK_PRE)}`;
|
|
311
|
+
const postCmd = `node ${path.join(hooksDir, HOOK_POST)}`;
|
|
312
|
+
return {
|
|
313
|
+
PreToolUse: [{ matcher: PRE_MATCHER, hooks: [{ type: 'command', command: preCmd }] }],
|
|
314
|
+
PostToolUse: [{ matcher: postMatcher, hooks: [{ type: 'command', command: postCmd }] }],
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
/** Merge our hook entries into an existing `hooks` config object, stripping any
|
|
318
|
+
* prior coldstart entries first (idempotent re-run) and preserving foreign ones. */
|
|
319
|
+
function mergeHooks(hooksCfg, entries) {
|
|
320
|
+
const stripOurs = (arr) => (Array.isArray(arr) ? arr : []).filter((e) => !isColdstartHookEntry(e));
|
|
321
|
+
hooksCfg.PreToolUse = [...stripOurs(hooksCfg.PreToolUse), ...entries.PreToolUse];
|
|
322
|
+
hooksCfg.PostToolUse = [...stripOurs(hooksCfg.PostToolUse), ...entries.PostToolUse];
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Register the find/gs search hooks in the project's `.claude/settings.json`.
|
|
326
|
+
*
|
|
327
|
+
* Merges (never clobbers): preserves every other setting and any non-coldstart
|
|
328
|
+
* hooks. Idempotent — strips our prior entries (matched by entry filename) and
|
|
329
|
+
* re-adds them, so a re-run refreshes a stale hook path without duplicating.
|
|
330
|
+
* Fail-safe — if settings.json exists but is not valid JSON, we leave it alone
|
|
331
|
+
* and report, rather than overwrite a file the user owns.
|
|
332
|
+
*/
|
|
333
|
+
export function wireClaudeHooks(cwd) {
|
|
334
|
+
let hooksDir;
|
|
335
|
+
try {
|
|
336
|
+
hooksDir = resolveHooksDir();
|
|
337
|
+
}
|
|
338
|
+
catch (e) {
|
|
339
|
+
return { error: `could not resolve a stable install path (${e})` };
|
|
340
|
+
}
|
|
341
|
+
const dir = path.join(cwd, '.claude');
|
|
342
|
+
const filePath = path.join(dir, 'settings.json');
|
|
343
|
+
let settings = {};
|
|
344
|
+
const existed = fs.existsSync(filePath);
|
|
345
|
+
if (existed) {
|
|
346
|
+
try {
|
|
347
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
348
|
+
if (parsed && typeof parsed === 'object')
|
|
349
|
+
settings = parsed;
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
return { error: `${filePath} is not valid JSON — left untouched; wire hooks manually` };
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
const hooksCfg = settings.hooks && typeof settings.hooks === 'object'
|
|
356
|
+
? settings.hooks
|
|
357
|
+
: {};
|
|
358
|
+
mergeHooks(hooksCfg, coldstartHooks(hooksDir, '*'));
|
|
359
|
+
settings.hooks = hooksCfg;
|
|
360
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
361
|
+
fs.writeFileSync(filePath, JSON.stringify(settings, null, 2) + '\n');
|
|
362
|
+
return existed ? 'updated' : 'created';
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Register Codex-specific navigation and notebook hooks. Event envelopes are
|
|
366
|
+
* intentionally handled by separate files so Codex rollout/subagent behavior
|
|
367
|
+
* can evolve without risking the Claude integration.
|
|
368
|
+
*/
|
|
369
|
+
export function wireCodexHooks(cwd) {
|
|
370
|
+
let hooksDir;
|
|
371
|
+
try {
|
|
372
|
+
hooksDir = resolveHooksDir();
|
|
373
|
+
}
|
|
374
|
+
catch (e) {
|
|
375
|
+
return { error: `could not resolve a stable install path (${e})` };
|
|
376
|
+
}
|
|
377
|
+
const dir = path.join(cwd, '.codex');
|
|
378
|
+
const filePath = path.join(dir, 'hooks.json');
|
|
379
|
+
let config = {};
|
|
380
|
+
const existed = fs.existsSync(filePath);
|
|
381
|
+
if (existed) {
|
|
382
|
+
try {
|
|
383
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
384
|
+
if (parsed && typeof parsed === 'object')
|
|
385
|
+
config = parsed;
|
|
386
|
+
}
|
|
387
|
+
catch {
|
|
388
|
+
return { error: `${filePath} is not valid JSON — left untouched; wire hooks manually` };
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
const hooksCfg = config.hooks && typeof config.hooks === 'object'
|
|
392
|
+
? config.hooks
|
|
393
|
+
: {};
|
|
394
|
+
const stripOurs = (arr) => (Array.isArray(arr) ? arr : []).filter((entry) => !isCodexHookEntry(entry));
|
|
395
|
+
const command = (file) => ({ type: 'command', command: `node ${path.join(hooksDir, file)}` });
|
|
396
|
+
hooksCfg.PreToolUse = [
|
|
397
|
+
...stripOurs(hooksCfg.PreToolUse),
|
|
398
|
+
{ matcher: PRE_MATCHER, hooks: [command(CODEX_HOOK_PRE)] },
|
|
399
|
+
];
|
|
400
|
+
hooksCfg.PostToolUse = [
|
|
401
|
+
...stripOurs(hooksCfg.PostToolUse),
|
|
402
|
+
{ matcher: '.*', hooks: [command(CODEX_HOOK_POST)] },
|
|
403
|
+
];
|
|
404
|
+
hooksCfg.UserPromptSubmit = [
|
|
405
|
+
...stripOurs(hooksCfg.UserPromptSubmit),
|
|
406
|
+
{ hooks: [command(CODEX_KB_HOOK_RECALL)] },
|
|
407
|
+
];
|
|
408
|
+
hooksCfg.Stop = [
|
|
409
|
+
...stripOurs(hooksCfg.Stop),
|
|
410
|
+
{ hooks: [command(CODEX_KB_HOOK_ELICIT)] },
|
|
411
|
+
];
|
|
412
|
+
hooksCfg.SubagentStop = [
|
|
413
|
+
...stripOurs(hooksCfg.SubagentStop),
|
|
414
|
+
{ matcher: '.*', hooks: [command(CODEX_KB_HOOK_ELICIT)] },
|
|
415
|
+
];
|
|
416
|
+
config.hooks = hooksCfg;
|
|
417
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
418
|
+
fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n');
|
|
419
|
+
return existed ? 'updated' : 'created';
|
|
420
|
+
}
|
|
421
|
+
/** True if a `.cursor/hooks.json` entry (`{command}`) is one WE wrote. Cursor's
|
|
422
|
+
* entry shape is flatter than Codex's ({command} vs {matcher,hooks:[…]}), so it
|
|
423
|
+
* gets its own detector. */
|
|
424
|
+
function isCursorHookEntry(entry) {
|
|
425
|
+
const cmd = entry?.command;
|
|
426
|
+
const owned = [CURSOR_HOOK_PRE, CURSOR_HOOK_POST, CURSOR_KB_HOOK_RECALL, CURSOR_KB_HOOK_ELICIT];
|
|
427
|
+
return typeof cmd === 'string' && owned.some((file) => cmd.includes(file));
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Register Cursor navigation + notebook hooks in `.cursor/hooks.json`.
|
|
431
|
+
*
|
|
432
|
+
* Cursor's config differs from Codex/Claude: a top-level `version` + a `hooks`
|
|
433
|
+
* map whose events hold FLAT `{command}` entries (no matcher / no nested hooks).
|
|
434
|
+
* `preToolUse`/`postToolUse` are generic (fire for Shell + Read + MCP), so no
|
|
435
|
+
* matcher is needed — the handler filters. The SAME hooks serve both experiences;
|
|
436
|
+
* the handlers normalize a Shell `coldstart find` (cli) and an MCP find (mcp) to
|
|
437
|
+
* one shape, mirroring Codex. Recall rides `beforeSubmitPrompt` (Cursor honors
|
|
438
|
+
* its `additional_context` despite the docs); capture rides `stop`+`subagentStop`.
|
|
439
|
+
*
|
|
440
|
+
* Merges idempotently (strips our prior entries by filename, preserves foreign
|
|
441
|
+
* ones) and is fail-safe on invalid JSON.
|
|
442
|
+
*/
|
|
443
|
+
export function wireCursorHooks(cwd) {
|
|
444
|
+
let hooksDir;
|
|
445
|
+
try {
|
|
446
|
+
hooksDir = resolveHooksDir();
|
|
447
|
+
}
|
|
448
|
+
catch (e) {
|
|
449
|
+
return { error: `could not resolve a stable install path (${e})` };
|
|
450
|
+
}
|
|
451
|
+
const dir = path.join(cwd, '.cursor');
|
|
452
|
+
const filePath = path.join(dir, 'hooks.json');
|
|
453
|
+
let config = {};
|
|
454
|
+
const existed = fs.existsSync(filePath);
|
|
455
|
+
if (existed) {
|
|
456
|
+
try {
|
|
457
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
458
|
+
if (parsed && typeof parsed === 'object')
|
|
459
|
+
config = parsed;
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
return { error: `${filePath} is not valid JSON — left untouched; wire hooks manually` };
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
const hooksCfg = config.hooks && typeof config.hooks === 'object'
|
|
466
|
+
? config.hooks
|
|
467
|
+
: {};
|
|
468
|
+
const stripOurs = (arr) => (Array.isArray(arr) ? arr : []).filter((entry) => !isCursorHookEntry(entry));
|
|
469
|
+
const command = (file) => ({ command: `node ${path.join(hooksDir, file)}` });
|
|
470
|
+
hooksCfg.preToolUse = [...stripOurs(hooksCfg.preToolUse), command(CURSOR_HOOK_PRE)];
|
|
471
|
+
hooksCfg.postToolUse = [...stripOurs(hooksCfg.postToolUse), command(CURSOR_HOOK_POST)];
|
|
472
|
+
hooksCfg.beforeSubmitPrompt = [...stripOurs(hooksCfg.beforeSubmitPrompt), command(CURSOR_KB_HOOK_RECALL)];
|
|
473
|
+
hooksCfg.stop = [...stripOurs(hooksCfg.stop), command(CURSOR_KB_HOOK_ELICIT)];
|
|
474
|
+
hooksCfg.subagentStop = [...stripOurs(hooksCfg.subagentStop), command(CURSOR_KB_HOOK_ELICIT)];
|
|
475
|
+
config.hooks = hooksCfg;
|
|
476
|
+
if (config.version === undefined)
|
|
477
|
+
config.version = 1;
|
|
478
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
479
|
+
fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n');
|
|
480
|
+
return existed ? 'updated' : 'created';
|
|
481
|
+
}
|
|
482
|
+
// ---------------------------------------------------------------------------
|
|
483
|
+
// Rules-file writers — each references coldstart.md, never duplicates it
|
|
484
|
+
// ---------------------------------------------------------------------------
|
|
485
|
+
/** Write `.cursor/rules/coldstart.mdc` — an always-applied rule that pulls in
|
|
486
|
+
* coldstart.md via Cursor's `@file` reference. Overwritten on re-run (it owns
|
|
487
|
+
* this file), so guidance edits flow through coldstart.md. */
|
|
488
|
+
export function wireCursorRule(cwd) {
|
|
489
|
+
const dir = path.join(cwd, '.cursor', 'rules');
|
|
490
|
+
const filePath = path.join(dir, 'coldstart.mdc');
|
|
491
|
+
const existed = fs.existsSync(filePath);
|
|
492
|
+
const body = `---
|
|
493
|
+
description: coldstart — fast codebase navigation (find/gs) before grep/read
|
|
494
|
+
alwaysApply: true
|
|
495
|
+
---
|
|
496
|
+
|
|
497
|
+
Before grepping or reading files to orient in this codebase, use coldstart
|
|
498
|
+
(\`find\` to locate files, \`gs\` to inspect one). Full guidance:
|
|
499
|
+
|
|
500
|
+
@coldstart.md
|
|
501
|
+
`;
|
|
502
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
503
|
+
fs.writeFileSync(filePath, body);
|
|
504
|
+
return existed ? 'updated' : 'created';
|
|
505
|
+
}
|
|
506
|
+
const AGENTS_START = '<!-- coldstart:start -->';
|
|
507
|
+
const AGENTS_END = '<!-- coldstart:end -->';
|
|
508
|
+
/** Ensure AGENTS.md carries a coldstart section pointing at coldstart.md.
|
|
509
|
+
* AGENTS.md has no import directive, so we inject a marked block and refresh it
|
|
510
|
+
* in place on re-run (idempotent), preserving everything else in the file. */
|
|
511
|
+
export function wireCodexAgents(cwd) {
|
|
512
|
+
const filePath = path.join(cwd, 'AGENTS.md');
|
|
513
|
+
const block = `${AGENTS_START}
|
|
514
|
+
## Codebase navigation (coldstart)
|
|
515
|
+
|
|
516
|
+
Before grepping or reading files to orient in this repo, use coldstart. Read
|
|
517
|
+
\`coldstart.md\` at the repo root for the find/gs workflow and follow it.
|
|
518
|
+
${AGENTS_END}`;
|
|
519
|
+
if (!fs.existsSync(filePath)) {
|
|
520
|
+
fs.writeFileSync(filePath, `# AGENTS.md\n\n${block}\n`);
|
|
521
|
+
return 'created';
|
|
522
|
+
}
|
|
523
|
+
const existing = fs.readFileSync(filePath, 'utf8');
|
|
524
|
+
const start = existing.indexOf(AGENTS_START);
|
|
525
|
+
if (start !== -1) {
|
|
526
|
+
const end = existing.indexOf(AGENTS_END, start);
|
|
527
|
+
if (end !== -1) {
|
|
528
|
+
const next = existing.slice(0, start) + block + existing.slice(end + AGENTS_END.length);
|
|
529
|
+
fs.writeFileSync(filePath, next);
|
|
530
|
+
return 'updated';
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
const sep = existing.endsWith('\n') ? '' : '\n';
|
|
534
|
+
fs.appendFileSync(filePath, `${sep}\n${block}\n`);
|
|
535
|
+
return 'updated';
|
|
536
|
+
}
|
|
537
|
+
// ---------------------------------------------------------------------------
|
|
538
|
+
// MCP config writers (only used for the `mcp` experience)
|
|
539
|
+
// ---------------------------------------------------------------------------
|
|
540
|
+
/** Merge `{ mcpServers: { coldstart: entry } }` into a JSON MCP config (used for
|
|
541
|
+
* Claude's `.mcp.json` and Cursor's `.cursor/mcp.json` — identical shape).
|
|
542
|
+
* Fail-safe on invalid JSON; preserves other servers and keys. */
|
|
543
|
+
export function wireJsonMcp(cwd, relFile, entry) {
|
|
544
|
+
const filePath = path.join(cwd, relFile);
|
|
545
|
+
let config = {};
|
|
546
|
+
const existed = fs.existsSync(filePath);
|
|
547
|
+
if (existed) {
|
|
548
|
+
try {
|
|
549
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
550
|
+
if (parsed && typeof parsed === 'object')
|
|
551
|
+
config = parsed;
|
|
552
|
+
}
|
|
553
|
+
catch {
|
|
554
|
+
return { error: `${filePath} is not valid JSON — left untouched; add the MCP server manually` };
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
const servers = config.mcpServers && typeof config.mcpServers === 'object'
|
|
558
|
+
? config.mcpServers
|
|
559
|
+
: {};
|
|
560
|
+
servers.coldstart = entry;
|
|
561
|
+
config.mcpServers = servers;
|
|
562
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
563
|
+
fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n');
|
|
564
|
+
return existed ? 'updated' : 'created';
|
|
565
|
+
}
|
|
566
|
+
/** Remove an existing `[mcp_servers.coldstart]` table (and any sub-tables) from
|
|
567
|
+
* TOML text, so we can append a fresh one without duplicating. Conservative:
|
|
568
|
+
* drops the header line and everything until the next top-level `[` or EOF. */
|
|
569
|
+
function stripCodexColdstartTable(toml) {
|
|
570
|
+
const lines = toml.split('\n');
|
|
571
|
+
const out = [];
|
|
572
|
+
let skipping = false;
|
|
573
|
+
const isColdstartHeader = (l) => /^\s*\[\[?mcp_servers\.coldstart(\..*)?\]\]?\s*$/.test(l);
|
|
574
|
+
const isTableHeader = (l) => /^\s*\[\[?[^\]]+\]\]?\s*$/.test(l);
|
|
575
|
+
for (const line of lines) {
|
|
576
|
+
if (skipping) {
|
|
577
|
+
if (isColdstartHeader(line))
|
|
578
|
+
continue; // a coldstart sub-table — keep skipping
|
|
579
|
+
if (isTableHeader(line))
|
|
580
|
+
skipping = false; // a different table — stop skipping
|
|
581
|
+
else
|
|
582
|
+
continue; // body of the coldstart table — drop
|
|
583
|
+
}
|
|
584
|
+
if (isColdstartHeader(line)) {
|
|
585
|
+
skipping = true;
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
out.push(line);
|
|
589
|
+
}
|
|
590
|
+
return out.join('\n');
|
|
591
|
+
}
|
|
592
|
+
/** Write `[mcp_servers.coldstart]` into `.codex/config.toml` (Codex's MCP config).
|
|
593
|
+
* Hand-rolled minimal TOML merge — no dependency: strip a prior coldstart table,
|
|
594
|
+
* then append a fresh one. Other config is preserved verbatim. */
|
|
595
|
+
export function wireCodexMcp(cwd, entry) {
|
|
596
|
+
const dir = path.join(cwd, '.codex');
|
|
597
|
+
const filePath = path.join(dir, 'config.toml');
|
|
598
|
+
const existed = fs.existsSync(filePath);
|
|
599
|
+
let content = existed ? fs.readFileSync(filePath, 'utf8') : '';
|
|
600
|
+
content = stripCodexColdstartTable(content).replace(/\n+$/, '');
|
|
601
|
+
const args = entry.args.map((a) => JSON.stringify(a)).join(', ');
|
|
602
|
+
const block = `[mcp_servers.coldstart]\ncommand = ${JSON.stringify(entry.command)}\nargs = [${args}]\n`;
|
|
603
|
+
const next = content.length ? `${content}\n\n${block}` : block;
|
|
604
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
605
|
+
fs.writeFileSync(filePath, next);
|
|
606
|
+
return existed ? 'updated' : 'created';
|
|
607
|
+
}
|
|
608
|
+
const FLAVOR = (exp) => (exp === 'cli' ? 'CLI flavor' : 'MCP flavor');
|
|
609
|
+
/** Resolve + report the MCP entry, or null + a printed reason on failure. */
|
|
610
|
+
function mcpEntryOrNull(cwd) {
|
|
611
|
+
try {
|
|
612
|
+
return mcpServerEntry(cwd);
|
|
613
|
+
}
|
|
614
|
+
catch (e) {
|
|
615
|
+
out(` (could not resolve a stable install path for the MCP server: ${e})`);
|
|
616
|
+
return null;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
function reportHooks(label, res) {
|
|
620
|
+
if (typeof res === 'object')
|
|
621
|
+
out(` ${label} — search hooks NOT wired: ${res.error}`);
|
|
622
|
+
else
|
|
623
|
+
out(` ${label} — ${res} find/gs search hooks (PreToolUse + PostToolUse)`);
|
|
624
|
+
}
|
|
625
|
+
function setupClaude(cwd, exp) {
|
|
626
|
+
out(` coldstart.md — ${writeColdstartMd(cwd, exp)} (${FLAVOR(exp)})`);
|
|
627
|
+
const imp = wireClaudeImport(cwd);
|
|
628
|
+
out(` CLAUDE.md — ${imp === 'present' ? 'already imports @coldstart.md' : `${imp} @coldstart.md import`}`);
|
|
629
|
+
if (exp === 'mcp') {
|
|
630
|
+
const entry = mcpEntryOrNull(cwd);
|
|
631
|
+
if (entry) {
|
|
632
|
+
const r = wireJsonMcp(cwd, '.mcp.json', entry);
|
|
633
|
+
out(typeof r === 'object' ? ` .mcp.json — NOT written: ${r.error}` : ` .mcp.json — ${r} coldstart MCP server`);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
reportHooks('settings.json', wireClaudeHooks(cwd));
|
|
637
|
+
const kb = wireClaudeKbHooks(cwd);
|
|
638
|
+
out(typeof kb === 'object'
|
|
639
|
+
? ` settings.json — notebook hooks NOT wired: ${kb.error}`
|
|
640
|
+
: ` settings.json — ${kb} notebook recall + capture hooks (UserPromptSubmit + Stop/SubagentStop)`);
|
|
641
|
+
}
|
|
642
|
+
function setupCodex(cwd, exp) {
|
|
643
|
+
out(` coldstart.md — ${writeColdstartMd(cwd, exp)} (${FLAVOR(exp)})`);
|
|
644
|
+
out(` AGENTS.md — ${wireCodexAgents(cwd)} coldstart navigation section`);
|
|
645
|
+
if (exp === 'mcp') {
|
|
646
|
+
const entry = mcpEntryOrNull(cwd);
|
|
647
|
+
if (entry)
|
|
648
|
+
out(` config.toml — ${wireCodexMcp(cwd, entry)} [mcp_servers.coldstart]`);
|
|
649
|
+
}
|
|
650
|
+
const hooks = wireCodexHooks(cwd);
|
|
651
|
+
out(typeof hooks === 'object'
|
|
652
|
+
? ` hooks.json — Codex hooks NOT wired: ${hooks.error}`
|
|
653
|
+
: ` hooks.json — ${hooks} Codex navigation + notebook hooks`);
|
|
654
|
+
}
|
|
655
|
+
function setupCursor(cwd, exp) {
|
|
656
|
+
out(` coldstart.md — ${writeColdstartMd(cwd, exp)} (${FLAVOR(exp)})`);
|
|
657
|
+
out(` coldstart.mdc — ${wireCursorRule(cwd)} .cursor/rules rule (references @coldstart.md)`);
|
|
658
|
+
if (exp === 'mcp') {
|
|
659
|
+
const entry = mcpEntryOrNull(cwd);
|
|
660
|
+
if (entry) {
|
|
661
|
+
const r = wireJsonMcp(cwd, path.join('.cursor', 'mcp.json'), entry);
|
|
662
|
+
out(typeof r === 'object' ? ` .cursor/mcp.json — NOT written: ${r.error}` : ` mcp.json — ${r} .cursor coldstart MCP server`);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
const hooks = wireCursorHooks(cwd);
|
|
666
|
+
out(typeof hooks === 'object'
|
|
667
|
+
? ` hooks.json — Cursor hooks NOT wired: ${hooks.error}`
|
|
668
|
+
: ` hooks.json — ${hooks} Cursor navigation + notebook hooks`);
|
|
669
|
+
}
|
|
670
|
+
function setupOther(cwd, exp) {
|
|
671
|
+
out(` coldstart.md — ${writeColdstartMd(cwd, exp)} (${FLAVOR(exp)})`);
|
|
672
|
+
out('');
|
|
673
|
+
out(' Wire it into your client:');
|
|
674
|
+
out(' 1. Add coldstart.md to your client\'s rules / instructions / skill');
|
|
675
|
+
out(' (whatever your app uses — point it at this file or paste its contents).');
|
|
676
|
+
if (exp === 'mcp') {
|
|
677
|
+
out(' 2. Add the MCP server entry to your client\'s MCP config:');
|
|
678
|
+
out('');
|
|
679
|
+
const entry = mcpEntryOrNull(cwd);
|
|
680
|
+
if (entry) {
|
|
681
|
+
out(' ' + JSON.stringify({ mcpServers: { coldstart: entry } }, null, 2).split('\n').join('\n '));
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
else {
|
|
685
|
+
out(' 2. Make sure the `coldstart` CLI is on PATH so the agent can run');
|
|
686
|
+
out(' `coldstart find` / `coldstart gs` (npm i -g @cstart/coldstart).');
|
|
687
|
+
}
|
|
688
|
+
out('');
|
|
689
|
+
out(' Note: behavioral hooks need a supported host. coldstart ships separate');
|
|
690
|
+
out(' hook implementations for Claude Code and Codex.');
|
|
691
|
+
}
|
|
692
|
+
// ---------------------------------------------------------------------------
|
|
693
|
+
// Entry point
|
|
694
|
+
// ---------------------------------------------------------------------------
|
|
695
|
+
/** Read `--flag value` or `--flag=value` from an argv slice. */
|
|
696
|
+
function readFlag(argv, flag) {
|
|
697
|
+
for (let i = 0; i < argv.length; i++) {
|
|
698
|
+
const a = argv[i];
|
|
699
|
+
if (a === flag)
|
|
700
|
+
return argv[i + 1]?.trim().toLowerCase();
|
|
701
|
+
if (a.startsWith(`${flag}=`))
|
|
702
|
+
return a.slice(flag.length + 1).trim().toLowerCase();
|
|
703
|
+
}
|
|
704
|
+
return undefined;
|
|
705
|
+
}
|
|
706
|
+
function parseExperience(v) {
|
|
707
|
+
if (v === 'cli' || v === '1')
|
|
708
|
+
return 'cli';
|
|
709
|
+
if (v === 'mcp' || v === '2')
|
|
710
|
+
return 'mcp';
|
|
711
|
+
return undefined;
|
|
712
|
+
}
|
|
713
|
+
function parseClient(v) {
|
|
714
|
+
if (v === 'claude' || v === 'claude-code' || v === '1')
|
|
715
|
+
return 'claude';
|
|
716
|
+
if (v === 'cursor' || v === '2')
|
|
717
|
+
return 'cursor';
|
|
718
|
+
if (v === 'codex' || v === '3')
|
|
719
|
+
return 'codex';
|
|
720
|
+
if (v === 'other' || v === 'others' || v === '4')
|
|
721
|
+
return 'other';
|
|
722
|
+
return undefined;
|
|
723
|
+
}
|
|
724
|
+
export async function runInit() {
|
|
725
|
+
const cwd = process.cwd();
|
|
726
|
+
const argv = process.argv.slice(3); // tokens after `init`
|
|
727
|
+
let experience = parseExperience(readFlag(argv, '--experience'));
|
|
728
|
+
let client = parseClient(readFlag(argv, '--client'));
|
|
729
|
+
// Storage default is private (notebook gitignored); --commit-notebook opts in
|
|
730
|
+
// to committing .raw so the notebook can be shared with the team.
|
|
731
|
+
const commitNotebook = argv.includes('--commit-notebook');
|
|
732
|
+
out('');
|
|
733
|
+
out('coldstart init');
|
|
734
|
+
out(DIVIDER);
|
|
735
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
736
|
+
const ask = async (q) => (await rl.question(q)).trim().toLowerCase();
|
|
737
|
+
if (!experience) {
|
|
738
|
+
out('');
|
|
739
|
+
out('How will the agent invoke coldstart?');
|
|
740
|
+
out('');
|
|
741
|
+
out(' 1 CLI (recommended) — runs `coldstart find` / `coldstart gs` (shell)');
|
|
742
|
+
out(' 2 MCP — calls the `find` / `gs` MCP tools (no shell)');
|
|
743
|
+
out('');
|
|
744
|
+
experience = parseExperience(await ask('Choose [1/2]: ')) ?? 'cli';
|
|
745
|
+
}
|
|
746
|
+
if (!client) {
|
|
747
|
+
out('');
|
|
748
|
+
out('Which client are you wiring coldstart into?');
|
|
749
|
+
out('');
|
|
750
|
+
out(' 1 Claude Code — rules import + find/gs hooks');
|
|
751
|
+
out(' 2 Cursor — rules + navigation/notebook hooks');
|
|
752
|
+
out(' 3 Codex — rules + navigation/notebook hooks');
|
|
753
|
+
out(' 4 Other — coldstart.md + wiring directions');
|
|
754
|
+
out('');
|
|
755
|
+
client = parseClient(await ask('Choose [1/2/3/4]: ')) ?? 'other';
|
|
756
|
+
}
|
|
757
|
+
rl.close();
|
|
758
|
+
out('');
|
|
759
|
+
switch (client) {
|
|
760
|
+
case 'claude':
|
|
761
|
+
setupClaude(cwd, experience);
|
|
762
|
+
break;
|
|
763
|
+
case 'cursor':
|
|
764
|
+
setupCursor(cwd, experience);
|
|
765
|
+
break;
|
|
766
|
+
case 'codex':
|
|
767
|
+
setupCodex(cwd, experience);
|
|
768
|
+
break;
|
|
769
|
+
default:
|
|
770
|
+
setupOther(cwd, experience);
|
|
771
|
+
}
|
|
772
|
+
// The notebook is always set up (files + git wiring). Claude and Codex hooks
|
|
773
|
+
// are wired by their client setup above; Cursor/other use the explicit CLI.
|
|
774
|
+
setupNotebook(cwd, commitNotebook);
|
|
775
|
+
// Warm the index now, while the user is here at setup, so the first lookup
|
|
776
|
+
// isn't a cold build. ensureKeeper spawns the background keeper (detached) and
|
|
777
|
+
// returns immediately; the keeper walks + indexes + watches from here on.
|
|
778
|
+
// Best-effort — a spawn failure just means the first query builds lazily.
|
|
779
|
+
out('');
|
|
780
|
+
out('Indexing this repo in the background — your first lookup will be instant.');
|
|
781
|
+
await ensureKeeper(cwd);
|
|
782
|
+
out('');
|
|
783
|
+
out(DIVIDER);
|
|
784
|
+
out('Done. Full docs: https://github.com/AkashGoenka/coldstart');
|
|
785
|
+
out('');
|
|
786
|
+
}
|
|
787
|
+
//# sourceMappingURL=init.js.map
|