@gurulu/cli 0.4.7 → 1.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/LICENSE +92 -0
- package/README.md +35 -106
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +25410 -0
- package/dist/commands/auth.d.ts +23 -20
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/doctor.d.ts +20 -6
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/init.d.ts +25 -11
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/pull.d.ts +13 -0
- package/dist/commands/pull.d.ts.map +1 -0
- package/dist/commands/push.d.ts +40 -0
- package/dist/commands/push.d.ts.map +1 -0
- package/dist/commands/validate.d.ts +36 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24985 -876
- package/dist/lib/api.d.ts +139 -0
- package/dist/lib/api.d.ts.map +1 -0
- package/dist/lib/codegen.d.ts +4 -0
- package/dist/lib/codegen.d.ts.map +1 -0
- package/dist/lib/config.d.ts +43 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/package.json +40 -20
- package/bin/gurulu.js +0 -2
- package/dist/api-client.d.ts +0 -33
- package/dist/api-client.js +0 -175
- package/dist/commands/add-server.d.ts +0 -9
- package/dist/commands/add-server.js +0 -162
- package/dist/commands/alerts.d.ts +0 -27
- package/dist/commands/alerts.js +0 -309
- package/dist/commands/api-keys.d.ts +0 -20
- package/dist/commands/api-keys.js +0 -130
- package/dist/commands/attribution.d.ts +0 -22
- package/dist/commands/attribution.js +0 -111
- package/dist/commands/audiences.d.ts +0 -23
- package/dist/commands/audiences.js +0 -243
- package/dist/commands/audit.d.ts +0 -20
- package/dist/commands/audit.js +0 -130
- package/dist/commands/auth.js +0 -249
- package/dist/commands/chat.d.ts +0 -19
- package/dist/commands/chat.js +0 -118
- package/dist/commands/config.d.ts +0 -10
- package/dist/commands/config.js +0 -92
- package/dist/commands/consent.d.ts +0 -27
- package/dist/commands/consent.js +0 -233
- package/dist/commands/conversion-paths.d.ts +0 -19
- package/dist/commands/conversion-paths.js +0 -55
- package/dist/commands/db.d.ts +0 -25
- package/dist/commands/db.js +0 -330
- package/dist/commands/destinations.d.ts +0 -20
- package/dist/commands/destinations.js +0 -191
- package/dist/commands/doctor.js +0 -360
- package/dist/commands/errors.d.ts +0 -27
- package/dist/commands/errors.js +0 -121
- package/dist/commands/events.d.ts +0 -33
- package/dist/commands/events.js +0 -371
- package/dist/commands/experiments.d.ts +0 -22
- package/dist/commands/experiments.js +0 -264
- package/dist/commands/funnels.d.ts +0 -17
- package/dist/commands/funnels.js +0 -203
- package/dist/commands/goals.d.ts +0 -18
- package/dist/commands/goals.js +0 -214
- package/dist/commands/heatmap.d.ts +0 -27
- package/dist/commands/heatmap.js +0 -112
- package/dist/commands/identity.d.ts +0 -29
- package/dist/commands/identity.js +0 -328
- package/dist/commands/init.js +0 -215
- package/dist/commands/insights.d.ts +0 -10
- package/dist/commands/insights.js +0 -77
- package/dist/commands/install.d.ts +0 -259
- package/dist/commands/install.js +0 -1590
- package/dist/commands/login.d.ts +0 -20
- package/dist/commands/login.js +0 -170
- package/dist/commands/logout.d.ts +0 -10
- package/dist/commands/logout.js +0 -41
- package/dist/commands/playground.d.ts +0 -11
- package/dist/commands/playground.js +0 -47
- package/dist/commands/releases.d.ts +0 -17
- package/dist/commands/releases.js +0 -54
- package/dist/commands/replay.d.ts +0 -18
- package/dist/commands/replay.js +0 -64
- package/dist/commands/secrets.d.ts +0 -19
- package/dist/commands/secrets.js +0 -145
- package/dist/commands/setup.d.ts +0 -21
- package/dist/commands/setup.js +0 -67
- package/dist/commands/sites.d.ts +0 -18
- package/dist/commands/sites.js +0 -139
- package/dist/commands/skad.d.ts +0 -18
- package/dist/commands/skad.js +0 -53
- package/dist/commands/sourcemap.d.ts +0 -33
- package/dist/commands/sourcemap.js +0 -204
- package/dist/commands/status.d.ts +0 -7
- package/dist/commands/status.js +0 -136
- package/dist/commands/upgrade.d.ts +0 -21
- package/dist/commands/upgrade.js +0 -183
- package/dist/commands/warehouse.d.ts +0 -20
- package/dist/commands/warehouse.js +0 -65
- package/dist/commands/warehouses.d.ts +0 -17
- package/dist/commands/warehouses.js +0 -182
- package/dist/commands/watch.d.ts +0 -45
- package/dist/commands/watch.js +0 -258
- package/dist/commands/whoami.d.ts +0 -9
- package/dist/commands/whoami.js +0 -50
- package/dist/config.d.ts +0 -75
- package/dist/config.js +0 -329
- package/dist/frameworks/detect.d.ts +0 -8
- package/dist/frameworks/detect.js +0 -458
- package/dist/install-intent-proposal.d.ts +0 -99
- package/dist/install-intent-proposal.js +0 -202
- package/dist/utils/api.d.ts +0 -20
- package/dist/utils/api.js +0 -47
- package/dist/utils/config.d.ts +0 -13
- package/dist/utils/config.js +0 -30
- package/dist/utils/confirm.d.ts +0 -17
- package/dist/utils/confirm.js +0 -40
- package/dist/utils/dry-run.d.ts +0 -20
- package/dist/utils/dry-run.js +0 -67
- package/dist/utils/from-file.d.ts +0 -9
- package/dist/utils/from-file.js +0 -72
- package/dist/utils/redact.d.ts +0 -14
- package/dist/utils/redact.js +0 -48
- package/dist/utils/ui.d.ts +0 -14
- package/dist/utils/ui.js +0 -59
- package/scripts/.gitkeep +0 -0
- package/scripts/README-gurulu-agentic-install.md +0 -114
- package/scripts/README-gurulu-scan.md +0 -98
- package/scripts/audit-cli-scopes.mjs +0 -204
- package/scripts/backfill-tenant-id.mjs +0 -172
- package/scripts/backfill-tenant-links.ts +0 -252
- package/scripts/backup-clickhouse.sh +0 -27
- package/scripts/backup-postgres.sh +0 -19
- package/scripts/bootstrap-runtime-schema.mjs +0 -87
- package/scripts/bootstrap-stripe.mjs +0 -158
- package/scripts/gurulu-agentic-install.lib.cjs +0 -762
- package/scripts/gurulu-agentic-install.mjs +0 -623
- package/scripts/gurulu-scan.lib.cjs +0 -1509
- package/scripts/gurulu-scan.mjs +0 -91
- package/scripts/gurulu-verify-install.lib.cjs +0 -334
- package/scripts/gurulu-verify-install.mjs +0 -59
- package/scripts/init-ssl.sh +0 -26
- package/scripts/migrate-flow-graph-enums.sh +0 -86
- package/scripts/monitor-disk.sh +0 -24
- package/scripts/patches/astro.patch.cjs +0 -74
- package/scripts/patches/auto-instrument/ast-helper.cjs +0 -480
- package/scripts/patches/auto-instrument/astro.cjs +0 -273
- package/scripts/patches/auto-instrument/express.cjs +0 -383
- package/scripts/patches/auto-instrument/fastify.cjs +0 -262
- package/scripts/patches/auto-instrument/hono.cjs +0 -392
- package/scripts/patches/auto-instrument/index.cjs +0 -80
- package/scripts/patches/auto-instrument/nestjs.cjs +0 -286
- package/scripts/patches/auto-instrument/nextjs-app-router.cjs +0 -345
- package/scripts/patches/auto-instrument/nextjs-pages.cjs +0 -361
- package/scripts/patches/auto-instrument/remix.cjs +0 -168
- package/scripts/patches/auto-instrument/sdk-helper-map.cjs +0 -241
- package/scripts/patches/auto-instrument/singleton-helper.cjs +0 -193
- package/scripts/patches/auto-instrument/sveltekit.cjs +0 -161
- package/scripts/patches/auto-instrument/vite-react.cjs +0 -37
- package/scripts/patches/auto-instrument/vue.cjs +0 -196
- package/scripts/patches/express.patch.cjs +0 -99
- package/scripts/patches/fastify.patch.cjs +0 -108
- package/scripts/patches/index.cjs +0 -300
- package/scripts/patches/nestjs.patch.cjs +0 -112
- package/scripts/patches/nextjs-app-router.patch.cjs +0 -97
- package/scripts/patches/nextjs-pages.patch.cjs +0 -97
- package/scripts/patches/remix.patch.cjs +0 -75
- package/scripts/patches/sveltekit.patch.cjs +0 -72
- package/scripts/patches/vite-react.patch.cjs +0 -73
- package/scripts/patches/vue.patch.cjs +0 -82
- package/scripts/renew-ssl.sh +0 -14
- package/scripts/resolve-migration.sh +0 -23
- package/scripts/seed-cli-dev-keys.mjs +0 -130
- package/scripts/seed-test-data.mjs +0 -391
- package/scripts/spike-browserless.ts +0 -65
- package/scripts/tenant-pivot-consistency-check.mjs +0 -205
- package/scripts/tenant-pivot-phase-3-cleanup.lib.cjs +0 -258
- package/scripts/tenant-pivot-phase-3-cleanup.mjs +0 -98
- package/scripts/test-identity-resolution.ts +0 -804
- package/scripts/validate-gurulu-schemas.mjs +0 -79
package/dist/utils/ui.js
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.dim = exports.bold = exports.cyan = exports.yellow = exports.red = exports.green = void 0;
|
|
4
|
-
exports.success = success;
|
|
5
|
-
exports.error = error;
|
|
6
|
-
exports.warn = warn;
|
|
7
|
-
exports.info = info;
|
|
8
|
-
exports.step = step;
|
|
9
|
-
exports.banner = banner;
|
|
10
|
-
exports.prompt = prompt;
|
|
11
|
-
exports.promptSelect = promptSelect;
|
|
12
|
-
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
13
|
-
exports.green = green;
|
|
14
|
-
const red = (s) => `\x1b[31m${s}\x1b[0m`;
|
|
15
|
-
exports.red = red;
|
|
16
|
-
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
17
|
-
exports.yellow = yellow;
|
|
18
|
-
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
19
|
-
exports.cyan = cyan;
|
|
20
|
-
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
21
|
-
exports.bold = bold;
|
|
22
|
-
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
23
|
-
exports.dim = dim;
|
|
24
|
-
function success(msg) { console.log(`${(0, exports.green)('\u2713')} ${msg}`); }
|
|
25
|
-
function error(msg) { console.log(`${(0, exports.red)('\u2717')} ${msg}`); }
|
|
26
|
-
function warn(msg) { console.log(`${(0, exports.yellow)('\u26A0')} ${msg}`); }
|
|
27
|
-
function info(msg) { console.log(`${(0, exports.cyan)('\u2139')} ${msg}`); }
|
|
28
|
-
function step(msg) { console.log(` ${(0, exports.dim)('\u2192')} ${msg}`); }
|
|
29
|
-
function banner() {
|
|
30
|
-
console.log('');
|
|
31
|
-
console.log((0, exports.bold)(' Gurulu.io'));
|
|
32
|
-
console.log((0, exports.dim)(' Autonomous Analytics Setup'));
|
|
33
|
-
console.log('');
|
|
34
|
-
}
|
|
35
|
-
function prompt(question) {
|
|
36
|
-
return new Promise((resolve) => {
|
|
37
|
-
const readline = require('readline');
|
|
38
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
39
|
-
rl.question(question, (answer) => {
|
|
40
|
-
rl.close();
|
|
41
|
-
resolve(answer.trim());
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
function promptSelect(question, options) {
|
|
46
|
-
return new Promise((resolve) => {
|
|
47
|
-
options.forEach((opt, i) => {
|
|
48
|
-
console.log(` ${(0, exports.dim)(`${i + 1}.`)} ${opt}`);
|
|
49
|
-
});
|
|
50
|
-
console.log('');
|
|
51
|
-
const readline = require('readline');
|
|
52
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
53
|
-
rl.question(question, (answer) => {
|
|
54
|
-
rl.close();
|
|
55
|
-
const idx = parseInt(answer.trim(), 10) - 1;
|
|
56
|
-
resolve(idx >= 0 && idx < options.length ? idx : 0);
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
}
|
package/scripts/.gitkeep
DELETED
|
File without changes
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
# gurulu-agentic-install (Phase 14 B3 alpha)
|
|
2
|
-
|
|
3
|
-
Takes a **gurulu-scan** JSON document (Phase 13 B2) and produces the 10
|
|
4
|
-
`.gurulu/` artifacts consumed by the Phase 13 B1 runtime loader. This is the
|
|
5
|
-
**alpha** — config-only, no source code edits and no SDK injection.
|
|
6
|
-
|
|
7
|
-
## Pipeline
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
your repo
|
|
11
|
-
|
|
|
12
|
-
| node scripts/gurulu-scan.mjs . (Phase 13 B2)
|
|
13
|
-
v
|
|
14
|
-
scan-output.json
|
|
15
|
-
|
|
|
16
|
-
| node scripts/gurulu-agentic-install.mjs (Phase 14 B3 — this tool)
|
|
17
|
-
v
|
|
18
|
-
.gurulu/
|
|
19
|
-
install-plan.json
|
|
20
|
-
core-config.json
|
|
21
|
-
web.config.json
|
|
22
|
-
app.config.json
|
|
23
|
-
server-map.json
|
|
24
|
-
db-map.json
|
|
25
|
-
flow-seeds.json
|
|
26
|
-
milestone-rules.json
|
|
27
|
-
correlation-map.json
|
|
28
|
-
connectors.json
|
|
29
|
-
|
|
|
30
|
-
| src/lib/gurulu-config/loader.ts (Phase 13 B1)
|
|
31
|
-
v
|
|
32
|
-
Runtime ready
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## Usage
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
# 1. Scan the target repo
|
|
39
|
-
node scripts/gurulu-scan.mjs /path/to/repo --output scan.json --quiet
|
|
40
|
-
|
|
41
|
-
# 2. Generate .gurulu/ artifacts
|
|
42
|
-
node scripts/gurulu-agentic-install.mjs scan.json \
|
|
43
|
-
--site-id site_acme --tenant-id tnt_acme \
|
|
44
|
-
--output .gurulu
|
|
45
|
-
|
|
46
|
-
# Optional: preview without writing
|
|
47
|
-
node scripts/gurulu-agentic-install.mjs scan.json \
|
|
48
|
-
--site-id site_acme --tenant-id tnt_acme --dry-run
|
|
49
|
-
|
|
50
|
-
# stdin input
|
|
51
|
-
cat scan.json | node scripts/gurulu-agentic-install.mjs - \
|
|
52
|
-
--site-id site_acme --tenant-id tnt_acme
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
Flags:
|
|
56
|
-
|
|
57
|
-
| flag | purpose |
|
|
58
|
-
| --------------- | ------------------------------------------------------------- |
|
|
59
|
-
| `--site-id` | stable site identifier (required) |
|
|
60
|
-
| `--tenant-id` | tenant identifier (required) |
|
|
61
|
-
| `--output` | output directory (default `.gurulu/`) |
|
|
62
|
-
| `--domains` | comma-separated hosts for `install-plan.domains` |
|
|
63
|
-
| `--dry-run` | validate + report without writing |
|
|
64
|
-
| `--quiet` | suppress non-error output |
|
|
65
|
-
|
|
66
|
-
## What the alpha does
|
|
67
|
-
|
|
68
|
-
- Validates every generated artifact against
|
|
69
|
-
`schemas/gurulu/*.schema.json` (Phase 10 W4.1) before writing.
|
|
70
|
-
Atomic: on any validation error nothing is written.
|
|
71
|
-
- Derives **business surfaces** from the scanner's `routes` + `mutations`
|
|
72
|
-
(signup / checkout / subscription / lead / onboarding) via keyword
|
|
73
|
-
heuristics.
|
|
74
|
-
- Produces **route → flow** suggestions for `web.config.json`.
|
|
75
|
-
- Produces **endpoint → event** suggestions for `server-map.json` (POST
|
|
76
|
-
verbs only).
|
|
77
|
-
- Produces **table → event** suggestions for `db-map.json` from ORM
|
|
78
|
-
mutations (Prisma / Mongoose / Drizzle / raw SQL via the scanner).
|
|
79
|
-
- Produces **flow seeds** and **milestone rules** per surface.
|
|
80
|
-
- Produces a **correlation map** bridging client → server → db events.
|
|
81
|
-
- Produces a **connectors** placeholder (empty — populated via dashboard).
|
|
82
|
-
- Produces a mobile SDK **skeleton** (`app.config.json` with `ios`
|
|
83
|
-
defaults). Alpha does not auto-detect mobile platforms.
|
|
84
|
-
|
|
85
|
-
## What the alpha does NOT do
|
|
86
|
-
|
|
87
|
-
- No source code edits. The tool never touches application source.
|
|
88
|
-
- No SDK install. It does not add dependencies or wire up imports.
|
|
89
|
-
- No CRM / outbound connector setup (`connectors.json` stays empty).
|
|
90
|
-
- No mobile platform detection.
|
|
91
|
-
- No code patches — that's Phase 15.
|
|
92
|
-
|
|
93
|
-
## Roadmap → full agentic install (Phase 15)
|
|
94
|
-
|
|
95
|
-
1. Auto-add Web / iOS / Android SDK imports to the host repo.
|
|
96
|
-
2. Auto-edit `layout.tsx` / `_app.tsx` / middleware to bootstrap the SDK.
|
|
97
|
-
3. Emit per-route + per-mutation code patches (PR-ready diffs).
|
|
98
|
-
4. Self-healing: re-scan on CI and update `.gurulu/` automatically.
|
|
99
|
-
5. Auto-detect mobile platforms + populate `app.config.json` properly.
|
|
100
|
-
6. Wire detected external services (Stripe / Shopify / Segment / HubSpot)
|
|
101
|
-
into `connectors.json`.
|
|
102
|
-
|
|
103
|
-
## Implementation notes
|
|
104
|
-
|
|
105
|
-
- Generator logic lives in `scripts/gurulu-agentic-install.lib.cjs` so it
|
|
106
|
-
can be `require()`'d from both the ESM CLI wrapper and ts-jest tests,
|
|
107
|
-
mirroring the layout of `scripts/gurulu-scan.lib.cjs`.
|
|
108
|
-
- Validation uses `ajv@6.14.0` already installed at the repo root.
|
|
109
|
-
- Every artifact is generated with sensible defaults when the scanner
|
|
110
|
-
supplies nothing (empty routes / mutations still yields a valid
|
|
111
|
-
bundle). `db-map.tables`, `flow-seeds.flows`, `milestone-rules.rules`
|
|
112
|
-
and `correlation-map.links` all have `minItems: 1` in their schemas;
|
|
113
|
-
the generator plants a placeholder entry rather than producing an
|
|
114
|
-
invalid document.
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
# gurulu-scan — repo scanner spike (Phase 13 B2)
|
|
2
|
-
|
|
3
|
-
`scripts/gurulu-scan.mjs` is a standalone Node script that scans any
|
|
4
|
-
repository path and emits a JSON document shaped like
|
|
5
|
-
[`install-plan.schema.json`](../schemas/gurulu/install-plan.schema.json). It is
|
|
6
|
-
the precursor to a real agentic installer: for now it **only gathers signal**
|
|
7
|
-
— it never writes code or touches the host repo.
|
|
8
|
-
|
|
9
|
-
## Usage
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
# Print the plan to stdout
|
|
13
|
-
node scripts/gurulu-scan.mjs <repo-path>
|
|
14
|
-
|
|
15
|
-
# Write to a file
|
|
16
|
-
node scripts/gurulu-scan.mjs <repo-path> --output /tmp/scan.json
|
|
17
|
-
|
|
18
|
-
# Suppress progress logs (useful when piping)
|
|
19
|
-
node scripts/gurulu-scan.mjs <repo-path> --quiet > /tmp/scan.json
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
Self-scan example:
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
node scripts/gurulu-scan.mjs . --quiet > /tmp/scan-self.json
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## What it detects
|
|
29
|
-
|
|
30
|
-
| Area | Signal | Notes |
|
|
31
|
-
| --- | --- | --- |
|
|
32
|
-
| Framework | `package.json` deps + devDeps | Next.js, Nest, Remix, SvelteKit, Astro, Fastify, Express, Vite + React, Vue |
|
|
33
|
-
| ORM | deps + schema files | Prisma (`prisma/schema.prisma`), Drizzle (`drizzle.config.*`), TypeORM, Mongoose, Sequelize, Kysely |
|
|
34
|
-
| Auth | deps | NextAuth, Clerk, Supabase (`+auth-helpers`), Firebase, Lucia, Passport, raw `jsonwebtoken` |
|
|
35
|
-
| Routes | filesystem walk | Next.js **App Router** (`**/app/api/**/route.{ts,js}`) and **Pages Router** (`**/pages/api/**`). Methods inferred from exported handlers / `req.method` checks. |
|
|
36
|
-
| Mutations | regex grep over `*.ts(x)`, `*.js(x)`, `*.mjs`, `*.cjs` | `prisma.<model>.{create,update,upsert,delete,...}`, `db.{insert,update,delete}(...)`, Mongoose `Model.{create,findOneAnd*,updateOne,...}`, raw `INSERT INTO / UPDATE / DELETE FROM` in template literals. De-duplicated and capped at 200 entries. |
|
|
37
|
-
|
|
38
|
-
## Output shape
|
|
39
|
-
|
|
40
|
-
```jsonc
|
|
41
|
-
{
|
|
42
|
-
"site_id": null,
|
|
43
|
-
"domains": [],
|
|
44
|
-
"framework": { "name": "nextjs", "version": "14.2.35" },
|
|
45
|
-
"orm": { "name": "prisma", "schemaPath": "prisma/schema.prisma" },
|
|
46
|
-
"auth": { "name": "nextauth" },
|
|
47
|
-
"routes": [
|
|
48
|
-
{ "path": "/api/users", "methods": ["GET", "POST"], "file": "app/api/users/route.ts" }
|
|
49
|
-
],
|
|
50
|
-
"businessSurfaces": [],
|
|
51
|
-
"mutations": [
|
|
52
|
-
{
|
|
53
|
-
"file": "app/api/users/route.ts",
|
|
54
|
-
"line": 12,
|
|
55
|
-
"model": "user",
|
|
56
|
-
"operation": "create",
|
|
57
|
-
"snippet": "const user = await prisma.user.create({..."
|
|
58
|
-
}
|
|
59
|
-
],
|
|
60
|
-
"scannedAt": "2026-04-13T12:34:56.000Z",
|
|
61
|
-
"scanVersion": "0.1.0"
|
|
62
|
-
}
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
Conceptually mapped to
|
|
66
|
-
[`schemas/gurulu/install-plan.schema.json`](../schemas/gurulu/install-plan.schema.json)
|
|
67
|
-
— the scanner emits the signal a later stage will transform into the schema's
|
|
68
|
-
`stack`, `surfaces` and `suggested_milestones`.
|
|
69
|
-
|
|
70
|
-
## Known limitations (spike scope)
|
|
71
|
-
|
|
72
|
-
- **No Express / Fastify / NestJS route detection.** Routes are returned as
|
|
73
|
-
`[]` with `routeDetectionNote: "detection_not_implemented_for_<framework>"`.
|
|
74
|
-
Handler introspection for imperative frameworks is a follow-up.
|
|
75
|
-
- **Mutation regex is best-effort.** It only matches syntactic patterns — it
|
|
76
|
-
has no semantic understanding of the code. Renamed clients (e.g.
|
|
77
|
-
`const p = prismaClient; p.user.create(...)`) will be missed.
|
|
78
|
-
- **No call-graph analysis.** Mutations are not mapped back to routes.
|
|
79
|
-
- **No `businessSurfaces` inference.** That lives in a later work unit — the
|
|
80
|
-
field is always emitted as `[]` for now.
|
|
81
|
-
- **`site_id` / `domains`** are null / empty — they are assigned by the
|
|
82
|
-
control plane, not discoverable locally.
|
|
83
|
-
- **No bundler / monorepo awareness.** It assumes a single app rooted at the
|
|
84
|
-
provided path. `src/` is preferred as the walk root when present, otherwise
|
|
85
|
-
the repo root is used.
|
|
86
|
-
|
|
87
|
-
## Implementation notes
|
|
88
|
-
|
|
89
|
-
Detection logic lives in `scripts/gurulu-scan.lib.cjs` (CommonJS) so that the
|
|
90
|
-
ESM `scripts/gurulu-scan.mjs` CLI wrapper and the ts-jest test suite
|
|
91
|
-
(`tests/gurulu-scan.test.ts`) can share a single module. The `.mjs` file
|
|
92
|
-
re-exports the library via `createRequire` and adds the CLI entry point.
|
|
93
|
-
|
|
94
|
-
Run the test suite with:
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
npx jest tests/gurulu-scan.test.ts
|
|
98
|
-
```
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Phase 20 W3 C2 — Scope audit script.
|
|
4
|
-
*
|
|
5
|
-
* Scans src/app/api/cli/** /route.ts for `requireCliAuth` calls, extracts
|
|
6
|
-
* the scope strings, and validates them against the catalog in
|
|
7
|
-
* src/lib/cli/scopes.ts. Fails with exit code 1 if any route uses an
|
|
8
|
-
* unknown scope or any scoped surface is missing a required scope.
|
|
9
|
-
*
|
|
10
|
-
* Flags:
|
|
11
|
-
* --json Emit JSON instead of the human table.
|
|
12
|
-
* --root <path> Override repo root (default: script parent).
|
|
13
|
-
*
|
|
14
|
-
* Exit codes:
|
|
15
|
-
* 0 — clean
|
|
16
|
-
* 1 — unknown scope or missing requireCliAuth
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import { promises as fs } from 'node:fs';
|
|
20
|
-
import path from 'node:path';
|
|
21
|
-
import { fileURLToPath } from 'node:url';
|
|
22
|
-
|
|
23
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
24
|
-
const __dirname = path.dirname(__filename);
|
|
25
|
-
|
|
26
|
-
const argv = process.argv.slice(2);
|
|
27
|
-
const jsonMode = argv.includes('--json');
|
|
28
|
-
const rootArgIdx = argv.indexOf('--root');
|
|
29
|
-
const repoRoot =
|
|
30
|
-
rootArgIdx >= 0 && argv[rootArgIdx + 1]
|
|
31
|
-
? path.resolve(argv[rootArgIdx + 1])
|
|
32
|
-
: path.resolve(__dirname, '..');
|
|
33
|
-
|
|
34
|
-
const SCOPES_FILE = path.join(repoRoot, 'src/lib/cli/scopes.ts');
|
|
35
|
-
const ROUTES_ROOT = path.join(repoRoot, 'src/app/api/cli');
|
|
36
|
-
|
|
37
|
-
// Routes that intentionally do not require CLI auth (public bootstrap
|
|
38
|
-
// endpoints). Anything else missing requireCliAuth fails the audit.
|
|
39
|
-
const UNAUTHED_ALLOWLIST = [
|
|
40
|
-
'verify-key/route.ts',
|
|
41
|
-
'device-link/start/route.ts',
|
|
42
|
-
'device-link/approve/route.ts',
|
|
43
|
-
'device-link/deny/route.ts',
|
|
44
|
-
'device-link/poll/route.ts',
|
|
45
|
-
];
|
|
46
|
-
|
|
47
|
-
async function loadScopeCatalog(file) {
|
|
48
|
-
const content = await fs.readFile(file, 'utf8');
|
|
49
|
-
const match = content.match(/CLI_SCOPES\s*=\s*\[([\s\S]*?)\]\s*as const/);
|
|
50
|
-
if (!match) {
|
|
51
|
-
throw new Error(`Cannot parse CLI_SCOPES from ${file}`);
|
|
52
|
-
}
|
|
53
|
-
const catalog = new Set();
|
|
54
|
-
const re = /'([^']+)'|"([^"]+)"/g;
|
|
55
|
-
let m;
|
|
56
|
-
while ((m = re.exec(match[1])) !== null) {
|
|
57
|
-
catalog.add(m[1] || m[2]);
|
|
58
|
-
}
|
|
59
|
-
return catalog;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async function walk(dir) {
|
|
63
|
-
const out = [];
|
|
64
|
-
let entries;
|
|
65
|
-
try {
|
|
66
|
-
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
67
|
-
} catch (err) {
|
|
68
|
-
if (err.code === 'ENOENT') return out;
|
|
69
|
-
throw err;
|
|
70
|
-
}
|
|
71
|
-
for (const entry of entries) {
|
|
72
|
-
const full = path.join(dir, entry.name);
|
|
73
|
-
if (entry.isDirectory()) {
|
|
74
|
-
out.push(...(await walk(full)));
|
|
75
|
-
} else if (entry.isFile() && entry.name === 'route.ts') {
|
|
76
|
-
out.push(full);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return out;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function extractScopes(source) {
|
|
83
|
-
// Matches both:
|
|
84
|
-
// requireCliAuth(req, { requiredScopes: ['a', 'b'] })
|
|
85
|
-
// requireWriteAuth(req, { requiredScopes: ['a:write'] }) // Phase 20 W2
|
|
86
|
-
// Multiline-tolerant via the [\s\S] dotall trick.
|
|
87
|
-
const calls = [];
|
|
88
|
-
const callRe =
|
|
89
|
-
/require(?:CliAuth|WriteAuth)\s*\([\s\S]*?requiredScopes\s*:\s*\[([^\]]*)\][\s\S]*?\)/g;
|
|
90
|
-
let m;
|
|
91
|
-
while ((m = callRe.exec(source)) !== null) {
|
|
92
|
-
const inner = m[1];
|
|
93
|
-
const scopes = [];
|
|
94
|
-
const strRe = /'([^']+)'|"([^"]+)"/g;
|
|
95
|
-
let s;
|
|
96
|
-
while ((s = strRe.exec(inner)) !== null) {
|
|
97
|
-
scopes.push(s[1] || s[2]);
|
|
98
|
-
}
|
|
99
|
-
calls.push(scopes);
|
|
100
|
-
}
|
|
101
|
-
const usesAuth = /require(?:CliAuth|WriteAuth)\s*\(/.test(source);
|
|
102
|
-
const hasBareCall = usesAuth && calls.length === 0;
|
|
103
|
-
return { scopedCalls: calls, hasBareCall, usesAuth };
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function relPath(repoRoot, full) {
|
|
107
|
-
return path.relative(repoRoot, full).split(path.sep).join('/');
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function allowlistMatch(rel) {
|
|
111
|
-
return UNAUTHED_ALLOWLIST.some((suffix) => rel.endsWith(`api/cli/${suffix}`));
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
async function main() {
|
|
115
|
-
let catalog;
|
|
116
|
-
try {
|
|
117
|
-
catalog = await loadScopeCatalog(SCOPES_FILE);
|
|
118
|
-
} catch (err) {
|
|
119
|
-
console.error(err.message);
|
|
120
|
-
process.exit(1);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const files = await walk(ROUTES_ROOT);
|
|
124
|
-
const rows = [];
|
|
125
|
-
const errors = [];
|
|
126
|
-
|
|
127
|
-
for (const file of files) {
|
|
128
|
-
const rel = relPath(repoRoot, file);
|
|
129
|
-
const source = await fs.readFile(file, 'utf8');
|
|
130
|
-
const { scopedCalls, hasBareCall, usesAuth } = extractScopes(source);
|
|
131
|
-
const allScopes = scopedCalls.flat();
|
|
132
|
-
const unknown = allScopes.filter((s) => !catalog.has(s));
|
|
133
|
-
void hasBareCall;
|
|
134
|
-
const routePath = rel
|
|
135
|
-
.replace(/^src\/app\/api\//, '/api/')
|
|
136
|
-
.replace(/\/route\.ts$/, '')
|
|
137
|
-
.replace(/\[([^\]]+)\]/g, ':$1');
|
|
138
|
-
|
|
139
|
-
if (allowlistMatch(rel)) {
|
|
140
|
-
rows.push({ route: routePath, file: rel, scopes: [], status: 'public' });
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (scopedCalls.length === 0 && !usesAuth) {
|
|
145
|
-
errors.push({ file: rel, error: 'missing_requireCliAuth' });
|
|
146
|
-
rows.push({
|
|
147
|
-
route: routePath,
|
|
148
|
-
file: rel,
|
|
149
|
-
scopes: [],
|
|
150
|
-
status: 'missing_auth',
|
|
151
|
-
});
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if (unknown.length > 0) {
|
|
156
|
-
errors.push({ file: rel, error: 'unknown_scope', scopes: unknown });
|
|
157
|
-
}
|
|
158
|
-
rows.push({
|
|
159
|
-
route: routePath,
|
|
160
|
-
file: rel,
|
|
161
|
-
scopes: allScopes,
|
|
162
|
-
status: unknown.length > 0 ? 'unknown_scope' : 'ok',
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
rows.sort((a, b) => a.route.localeCompare(b.route));
|
|
167
|
-
|
|
168
|
-
if (jsonMode) {
|
|
169
|
-
process.stdout.write(
|
|
170
|
-
JSON.stringify(
|
|
171
|
-
{ routes: rows, errors, catalogSize: catalog.size },
|
|
172
|
-
null,
|
|
173
|
-
2,
|
|
174
|
-
) + '\n',
|
|
175
|
-
);
|
|
176
|
-
} else {
|
|
177
|
-
const pad = (s, n) => s + ' '.repeat(Math.max(0, n - s.length));
|
|
178
|
-
process.stdout.write(
|
|
179
|
-
pad('ROUTE', 55) + pad('SCOPES', 45) + 'STATUS\n',
|
|
180
|
-
);
|
|
181
|
-
process.stdout.write('-'.repeat(110) + '\n');
|
|
182
|
-
for (const r of rows) {
|
|
183
|
-
process.stdout.write(
|
|
184
|
-
pad(r.route, 55) +
|
|
185
|
-
pad(r.scopes.join(',') || '-', 45) +
|
|
186
|
-
r.status +
|
|
187
|
-
'\n',
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
process.stdout.write(
|
|
191
|
-
`\n${rows.length} routes scanned, ${errors.length} errors.\n`,
|
|
192
|
-
);
|
|
193
|
-
for (const e of errors) {
|
|
194
|
-
process.stdout.write(` ✗ ${e.file}: ${e.error} ${JSON.stringify(e.scopes || '')}\n`);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
process.exit(errors.length > 0 ? 1 : 0);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
main().catch((err) => {
|
|
202
|
-
console.error(err);
|
|
203
|
-
process.exit(1);
|
|
204
|
-
});
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Phase 16 B2 — Backfill `tenant_id` on historical `events_raw` rows.
|
|
4
|
-
*
|
|
5
|
-
* For every site→tenant mapping in Prisma, issues one `ALTER TABLE ... UPDATE`
|
|
6
|
-
* per monthly partition (`toYYYYMM(date)`), chunked so a single mutation never
|
|
7
|
-
* has to scan the whole table. Dry-run by default — pass `--apply` to actually
|
|
8
|
-
* execute the mutations.
|
|
9
|
-
*
|
|
10
|
-
* Usage:
|
|
11
|
-
* node scripts/backfill-tenant-id.mjs # dry-run
|
|
12
|
-
* node scripts/backfill-tenant-id.mjs --apply # real run
|
|
13
|
-
* node scripts/backfill-tenant-id.mjs --partition 202604 # single month
|
|
14
|
-
*
|
|
15
|
-
* Env:
|
|
16
|
-
* CLICKHOUSE_URL, CLICKHOUSE_DATABASE
|
|
17
|
-
* BACKFILL_FAKE_SITE_MAP=1 — use an injected fake map instead of Prisma
|
|
18
|
-
* (used by tests / smoke tests).
|
|
19
|
-
*
|
|
20
|
-
* NOTE: intentionally NOT run in dev — this exists for ops to run once after
|
|
21
|
-
* the tenant_id column was added, against historical rows where tenant_id is
|
|
22
|
-
* empty/null. Do not invoke in CI.
|
|
23
|
-
*
|
|
24
|
-
* Phase 17 B1 update:
|
|
25
|
-
* The write path now stamps `tenant_id` natively on every new row via
|
|
26
|
-
* `src/lib/ingest/envelope-normalizer.ts` + `tenant-resolver.ts`. That
|
|
27
|
-
* means this backfill only needs to cover the historical window BEFORE
|
|
28
|
-
* Phase 17 B1 shipped — new ingests already land with tenant_id populated.
|
|
29
|
-
* If ops needs to re-run the backfill they can safely `--partition`-scope
|
|
30
|
-
* to the pre-cutover months only. See PHASE-17-ROADMAP.md §B1.
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
function parseArgs(argv) {
|
|
34
|
-
const args = { apply: false, partitions: null };
|
|
35
|
-
for (let i = 2; i < argv.length; i++) {
|
|
36
|
-
const a = argv[i];
|
|
37
|
-
if (a === '--apply') args.apply = true;
|
|
38
|
-
else if (a === '--partition') args.partitions = [argv[++i]];
|
|
39
|
-
else if (a === '--help' || a === '-h') args.help = true;
|
|
40
|
-
}
|
|
41
|
-
return args;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function printHelp() {
|
|
45
|
-
console.log(
|
|
46
|
-
`\nUsage: node scripts/backfill-tenant-id.mjs [--apply] [--partition YYYYMM]\n\n` +
|
|
47
|
-
` --apply Actually execute ALTER TABLE UPDATE. Default is dry-run.\n` +
|
|
48
|
-
` --partition Only process the given YYYYMM partition.\n`
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async function clickhouseQuery(query, params = {}) {
|
|
53
|
-
const CLICKHOUSE_URL = process.env.CLICKHOUSE_URL || 'http://localhost:8123';
|
|
54
|
-
const CLICKHOUSE_DB = process.env.CLICKHOUSE_DATABASE || 'gurulu';
|
|
55
|
-
const url = new URL(CLICKHOUSE_URL);
|
|
56
|
-
url.searchParams.set('database', CLICKHOUSE_DB);
|
|
57
|
-
url.searchParams.set('default_format', 'JSON');
|
|
58
|
-
for (const [k, v] of Object.entries(params)) {
|
|
59
|
-
url.searchParams.set(`param_${k}`, String(v));
|
|
60
|
-
}
|
|
61
|
-
const res = await fetch(url.toString(), {
|
|
62
|
-
method: 'POST',
|
|
63
|
-
body: query,
|
|
64
|
-
headers: { 'Content-Type': 'text/plain' },
|
|
65
|
-
});
|
|
66
|
-
if (!res.ok) throw new Error(`ClickHouse error ${res.status}: ${await res.text()}`);
|
|
67
|
-
return res.json();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function escapeSql(s) {
|
|
71
|
-
return String(s).replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Resolve the site→tenant map.
|
|
76
|
-
*
|
|
77
|
-
* Exported for tests — tests pass `fakeMap` directly; the real CLI uses
|
|
78
|
-
* Prisma unless BACKFILL_FAKE_SITE_MAP=1 (which makes the function return an
|
|
79
|
-
* in-memory fixture so scripts/tests can smoke-test the query builder without
|
|
80
|
-
* a DB).
|
|
81
|
-
*/
|
|
82
|
-
export async function loadSiteTenantMap(fakeMap) {
|
|
83
|
-
if (fakeMap) return fakeMap;
|
|
84
|
-
if (process.env.BACKFILL_FAKE_SITE_MAP === '1') {
|
|
85
|
-
return {
|
|
86
|
-
'site-fake-1': 'tenant-fake-A',
|
|
87
|
-
'site-fake-2': 'tenant-fake-A',
|
|
88
|
-
'site-fake-3': 'tenant-fake-B',
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
const { PrismaClient } = await import('@prisma/client');
|
|
92
|
-
const prisma = new PrismaClient();
|
|
93
|
-
try {
|
|
94
|
-
const sites = await prisma.site.findMany({ select: { id: true, tenantId: true } });
|
|
95
|
-
const map = {};
|
|
96
|
-
for (const s of sites) map[s.id] = s.tenantId;
|
|
97
|
-
return map;
|
|
98
|
-
} finally {
|
|
99
|
-
await prisma.$disconnect();
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Discover the list of monthly partitions currently present in `events_raw`.
|
|
105
|
-
*
|
|
106
|
-
* Exported for tests — accepts an optional injected query function.
|
|
107
|
-
*/
|
|
108
|
-
export async function discoverPartitions(queryFn = clickhouseQuery) {
|
|
109
|
-
const res = await queryFn(
|
|
110
|
-
`SELECT DISTINCT partition FROM system.parts WHERE table = 'events_raw' AND active ORDER BY partition`
|
|
111
|
-
);
|
|
112
|
-
return (res.data || []).map((r) => String(r.partition));
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Build the ALTER TABLE UPDATE SQL for one (partition, siteId→tenantId) chunk.
|
|
117
|
-
*
|
|
118
|
-
* Exported for tests.
|
|
119
|
-
*/
|
|
120
|
-
export function buildBackfillSql(partition, siteId, tenantId) {
|
|
121
|
-
return (
|
|
122
|
-
`ALTER TABLE events_raw UPDATE tenant_id = '${escapeSql(tenantId)}' ` +
|
|
123
|
-
`WHERE site_id = '${escapeSql(siteId)}' ` +
|
|
124
|
-
`AND (tenant_id = '' OR tenant_id IS NULL) ` +
|
|
125
|
-
`AND toYYYYMM(date) = ${Number(partition)}`
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
async function main() {
|
|
130
|
-
const args = parseArgs(process.argv);
|
|
131
|
-
if (args.help) {
|
|
132
|
-
printHelp();
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const mode = args.apply ? 'APPLY' : 'DRY-RUN';
|
|
137
|
-
console.log(`[backfill] mode=${mode}`);
|
|
138
|
-
|
|
139
|
-
const siteMap = await loadSiteTenantMap();
|
|
140
|
-
const siteIds = Object.keys(siteMap);
|
|
141
|
-
console.log(`[backfill] loaded ${siteIds.length} site→tenant mappings`);
|
|
142
|
-
|
|
143
|
-
const partitions = args.partitions || (await discoverPartitions());
|
|
144
|
-
console.log(`[backfill] ${partitions.length} partitions to process`);
|
|
145
|
-
|
|
146
|
-
let totalChunks = 0;
|
|
147
|
-
for (const partition of partitions) {
|
|
148
|
-
for (const siteId of siteIds) {
|
|
149
|
-
const tenantId = siteMap[siteId];
|
|
150
|
-
if (!tenantId) continue;
|
|
151
|
-
totalChunks++;
|
|
152
|
-
const sql = buildBackfillSql(partition, siteId, tenantId);
|
|
153
|
-
if (args.apply) {
|
|
154
|
-
console.log(`[backfill] APPLY partition=${partition} site=${siteId}`);
|
|
155
|
-
await clickhouseQuery(sql);
|
|
156
|
-
} else {
|
|
157
|
-
console.log(`[backfill] DRY partition=${partition} site=${siteId}`);
|
|
158
|
-
console.log(` ${sql}`);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
console.log(`[backfill] done — ${totalChunks} chunks ${args.apply ? 'applied' : '(dry-run)'}`);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const isMain = import.meta.url === `file://${process.argv[1]}`;
|
|
167
|
-
if (isMain) {
|
|
168
|
-
main().catch((err) => {
|
|
169
|
-
console.error('[backfill] fatal:', err);
|
|
170
|
-
process.exit(10);
|
|
171
|
-
});
|
|
172
|
-
}
|