@stonekeep/ai 0.0.0-stage → 0.8.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/dist/setup.js ADDED
@@ -0,0 +1,57 @@
1
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { StonekeepError, collectSchemaFields, loadConfig, readDataMap } from '@stonekeep/core';
3
+ import { parse as parseYaml } from 'yaml';
4
+ import { diffLines, renderDiff } from './diff.js';
5
+ import { buildSchemaMetadata } from './metadata.js';
6
+ import { buildPrompt } from './prompt.js';
7
+ import { renderProposal } from './render.js';
8
+ /**
9
+ * `stonekeep setup`: datamap -> schema metadata (guarded) -> provider ->
10
+ * validated YAML -> diff -> explicit "Y" -> save. Never saves without an
11
+ * explicit yes, and never in a non-interactive session.
12
+ */
13
+ export async function runSetup(options) {
14
+ const { io } = options;
15
+ const datamap = readDataMap(options.datamapPath);
16
+ const fields = collectSchemaFields(loadConfig(options.root)).fields;
17
+ const metadata = buildSchemaMetadata({ datamap, fields });
18
+ const provider = typeof options.provider === 'function' ? options.provider(metadata) : options.provider;
19
+ const columns = metadata.entities.reduce((n, e) => n + e.columns.length, 0);
20
+ io.stderr(`Sending schema metadata only (${metadata.entities.length} entities, ${columns} columns; no data values) to ${provider.name}...\n`);
21
+ const proposal = await provider.propose(buildPrompt(metadata));
22
+ let existingText = '';
23
+ let existing;
24
+ if (existsSync(options.connectorPath)) {
25
+ existingText = readFileSync(options.connectorPath, 'utf8');
26
+ const parsed = parseYaml(existingText);
27
+ if (typeof parsed === 'object' && parsed !== null)
28
+ existing = parsed;
29
+ }
30
+ const rendered = renderProposal(proposal, metadata, existing);
31
+ for (const w of rendered.warnings)
32
+ io.stderr(`warning: ${w}\n`);
33
+ const diff = diffLines(existingText, rendered.yaml);
34
+ const changed = diff.some((l) => l.op !== ' ');
35
+ io.stdout(`\nProposed changes to ${options.connectorPath}:\n\n`);
36
+ io.stdout(renderDiff(diff, io.color === true));
37
+ if (!changed) {
38
+ io.stdout('\nNo changes.\n');
39
+ return { saved: false, yaml: rendered.yaml, warnings: rendered.warnings, changed };
40
+ }
41
+ if (rendered.warnings.some((w) => w.startsWith('invalid:'))) {
42
+ throw new StonekeepError('CONFIG', 'The proposal is not a valid connector config; nothing was saved');
43
+ }
44
+ if (!io.ask) {
45
+ io.stdout('\nNot an interactive terminal: nothing was saved. Re-run in a terminal to confirm.\n');
46
+ return { saved: false, yaml: rendered.yaml, warnings: rendered.warnings, changed };
47
+ }
48
+ const answer = (await io.ask('\nSave these changes? Type Y to save, anything else to cancel: ')).trim();
49
+ if (answer !== 'Y' && answer !== 'y') {
50
+ io.stdout('Cancelled: nothing was saved.\n');
51
+ return { saved: false, yaml: rendered.yaml, warnings: rendered.warnings, changed };
52
+ }
53
+ writeFileSync(options.connectorPath, rendered.yaml, 'utf8');
54
+ io.stdout(`Saved ${options.connectorPath}. Review it, then run \`stonekeep discover\` to check it against the database.\n`);
55
+ return { saved: true, yaml: rendered.yaml, warnings: rendered.warnings, changed };
56
+ }
57
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC/F,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAuB,MAAM,eAAe,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AA0B7C;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,OAAqB;IAClD,MAAM,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC;IACvB,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,mBAAmB,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IACpE,MAAM,QAAQ,GAAG,mBAAmB,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IAC1D,MAAM,QAAQ,GACZ,OAAO,OAAO,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;IAEzF,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC5E,EAAE,CAAC,MAAM,CACP,iCAAiC,QAAQ,CAAC,QAAQ,CAAC,MAAM,cAAc,OAAO,gCAAgC,QAAQ,CAAC,IAAI,OAAO,CACnI,CAAC;IACF,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC;IAE/D,IAAI,YAAY,GAAG,EAAE,CAAC;IACtB,IAAI,QAA6C,CAAC;IAClD,IAAI,UAAU,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC;QACtC,YAAY,GAAG,YAAY,CAAC,OAAO,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;QAC3D,MAAM,MAAM,GAAY,SAAS,CAAC,YAAY,CAAC,CAAC;QAChD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;YAAE,QAAQ,GAAG,MAAiC,CAAC;IAClG,CAAC;IACD,MAAM,QAAQ,GAAG,cAAc,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC9D,KAAK,MAAM,CAAC,IAAI,QAAQ,CAAC,QAAQ;QAAE,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IAEhE,MAAM,IAAI,GAAG,SAAS,CAAC,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;IACpD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC;IAC/C,EAAE,CAAC,MAAM,CAAC,yBAAyB,OAAO,CAAC,aAAa,OAAO,CAAC,CAAC;IACjE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC;IAC/C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,EAAE,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;QAC7B,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,EAAE,CAAC;IACrF,CAAC;IACD,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC;QAC5D,MAAM,IAAI,cAAc,CACtB,QAAQ,EACR,iEAAiE,CAClE,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC;QACZ,EAAE,CAAC,MAAM,CACP,sFAAsF,CACvF,CAAC;QACF,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,EAAE,CAAC;IACrF,CAAC;IACD,MAAM,MAAM,GAAG,CACb,MAAM,EAAE,CAAC,GAAG,CAAC,iEAAiE,CAAC,CAChF,CAAC,IAAI,EAAE,CAAC;IACT,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACrC,EAAE,CAAC,MAAM,CAAC,iCAAiC,CAAC,CAAC;QAC7C,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,EAAE,CAAC;IACrF,CAAC;IACD,aAAa,CAAC,OAAO,CAAC,aAAa,EAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC5D,EAAE,CAAC,MAAM,CACP,SAAS,OAAO,CAAC,aAAa,kFAAkF,CACjH,CAAC;IACF,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,EAAE,CAAC;AACpF,CAAC"}
@@ -0,0 +1,5 @@
1
+ /** SKILL.md: instructions for AI coding agents working in a repository that uses Stonekeep. */
2
+ export declare function renderSkillMd(options?: {
3
+ connector?: string;
4
+ datamap?: string;
5
+ }): string;
package/dist/skill.js ADDED
@@ -0,0 +1,58 @@
1
+ /** SKILL.md: instructions for AI coding agents working in a repository that uses Stonekeep. */
2
+ export function renderSkillMd(options = {}) {
3
+ const connector = options.connector ?? 'stonekeep.connector.yaml';
4
+ const datamap = options.datamap ?? 'stonekeep-datamap.json';
5
+ return `---
6
+ name: stonekeep
7
+ description: Use when changing database schemas, migrations, ORM models or API schemas in this repository, or when asked about personal data, GDPR/CCPA requests, data deletion, exports or staging masking. Stonekeep tracks which fields hold personal data and enforces it in CI.
8
+ ---
9
+
10
+ # Stonekeep in this repository
11
+
12
+ Stonekeep maps personal data (PII) in the code and runs data-subject operations (export, deletion,
13
+ anonymization, staging masking) against the database. It helps teams manage personal data; it does
14
+ not guarantee legal compliance, so never claim that it does.
15
+
16
+ ## Files
17
+
18
+ | File | What it is | Who edits it |
19
+ | --- | --- | --- |
20
+ | \`${datamap}\` | Generated map of personal-data fields (field names only, no values) | \`stonekeep scan\` only; review diffs in PRs |
21
+ | \`stonekeep.config.json\` | Rules, excludes, per-field overrides | Humans; you may propose changes |
22
+ | \`${connector}\` | Database subject, relations, retention rules, masking, SaaS connectors | Humans; propose with \`stonekeep setup\` |
23
+
24
+ ## When you change a schema (migration, Prisma/TypeORM model, OpenAPI/JSON Schema)
25
+
26
+ 1. Run \`stonekeep scan\` and include the updated \`${datamap}\` in the same change.
27
+ 2. Run \`stonekeep check\`. Exit code 1 means a personal-data field is not mapped: fix it by
28
+ committing the regenerated datamap, or by adding an override in \`stonekeep.config.json\`
29
+ (\`"category"\`, or \`"ignore": true\` only if the field truly is not personal data).
30
+ 3. If you add a column that references the subject without a database foreign key (for example
31
+ \`requester_id\`), add it under \`tables.<table>.foreignKeys\` in \`${connector}\`, otherwise deletions
32
+ will miss those rows.
33
+ 4. If you add a table whose records must be kept for legal reasons (invoices, payments), add a
34
+ \`retain\` rule instead of letting deletions remove it.
35
+
36
+ ## Tools you may use
37
+
38
+ - \`stonekeep scan [path]\` / \`stonekeep check [path]\`: safe, local, read-only except the datamap.
39
+ - \`stonekeep discover\`: read-only catalog view of the configured database.
40
+ - \`stonekeep delete --subject-env VAR\` **without** \`--apply\`: a dry run that shows what would be deleted
41
+ or retained, with counts only.
42
+ - \`stonekeep mask\` **without** \`--apply\`: a dry run of the staging clone plan.
43
+ - \`stonekeep setup\`: proposes \`${connector}\` changes from schema metadata. It shows a diff and only
44
+ saves after a human types Y. Do not answer that prompt yourself.
45
+ - MCP server (\`stonekeep-mcp\`): \`scan_codebase\`, \`plan_compliance_yaml\`, \`dry_run_delete\`. There is
46
+ deliberately no tool that executes a deletion.
47
+
48
+ ## Never
49
+
50
+ - Never run \`stonekeep delete --apply\`, \`stonekeep mask --apply\`, or approve a request through the
51
+ Stonekeep API. Destructive operations require a human and, for deletions, a second approver.
52
+ - Never put real personal data (emails, names, ids from production) in code, tests, fixtures,
53
+ commit messages, issue text, or prompts. Use obviously fake values (example.com, 555 numbers).
54
+ - Never write secrets into config files. Credentials are references such as \`env:STRIPE_API_KEY\`.
55
+ - Never edit \`${datamap}\` by hand or delete entries to make \`stonekeep check\` pass.
56
+ `;
57
+ }
58
+ //# sourceMappingURL=skill.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill.js","sourceRoot":"","sources":["../src/skill.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAC/F,MAAM,UAAU,aAAa,CAAC,UAAoD,EAAE;IAClF,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,0BAA0B,CAAC;IAClE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,wBAAwB,CAAC;IAC5D,OAAO;;;;;;;;;;;;;;;MAeH,OAAO;;MAEP,SAAS;;;;sDAIuC,OAAO;;;;;yEAKY,SAAS;;;;;;;;;;;;oCAY9C,SAAS;;;;;;;;;;;;iBAY5B,OAAO;CACvB,CAAC;AACF,CAAC"}
@@ -0,0 +1,224 @@
1
+ {
2
+ "$comment": "Golden set for the setup-assistant prompt: 30 schema fields (names, types and scanner classification only) with the expected masking strategy. Expected values are reviewer judgment; 'keep' means not personal data.",
3
+ "subject": { "table": "customers", "key": "id" },
4
+ "retain": ["invoices"],
5
+ "fields": [
6
+ { "entity": "customers", "field": "id", "type": "integer", "rule": null, "expected": "keep" },
7
+ {
8
+ "entity": "customers",
9
+ "field": "email",
10
+ "type": "text",
11
+ "rule": "email",
12
+ "category": "contact",
13
+ "expected": "email"
14
+ },
15
+ {
16
+ "entity": "customers",
17
+ "field": "first_name",
18
+ "type": "text",
19
+ "rule": "person-name",
20
+ "category": "identity",
21
+ "expected": "first-name"
22
+ },
23
+ {
24
+ "entity": "customers",
25
+ "field": "last_name",
26
+ "type": "text",
27
+ "rule": "person-name",
28
+ "category": "identity",
29
+ "expected": "last-name"
30
+ },
31
+ {
32
+ "entity": "customers",
33
+ "field": "phone",
34
+ "type": "varchar(32)",
35
+ "rule": "phone",
36
+ "category": "contact",
37
+ "expected": "phone"
38
+ },
39
+ {
40
+ "entity": "customers",
41
+ "field": "date_of_birth",
42
+ "type": "date",
43
+ "rule": "date-of-birth",
44
+ "category": "demographic",
45
+ "expected": "date"
46
+ },
47
+ {
48
+ "entity": "customers",
49
+ "field": "signup_ip",
50
+ "type": "inet",
51
+ "rule": "online-identifier",
52
+ "category": "online_identifier",
53
+ "expected": "ip"
54
+ },
55
+ {
56
+ "entity": "customers",
57
+ "field": "passport_number",
58
+ "type": "varchar(20)",
59
+ "rule": "government-id",
60
+ "category": "government_id",
61
+ "expected": "token"
62
+ },
63
+ {
64
+ "entity": "customers",
65
+ "field": "stripe_customer_id",
66
+ "type": "text",
67
+ "rule": null,
68
+ "expected": "token"
69
+ },
70
+ {
71
+ "entity": "customers",
72
+ "field": "created_at",
73
+ "type": "timestamptz",
74
+ "rule": null,
75
+ "expected": "keep"
76
+ },
77
+ {
78
+ "entity": "customers",
79
+ "field": "marketing_opt_in",
80
+ "type": "boolean",
81
+ "rule": null,
82
+ "expected": "keep"
83
+ },
84
+ {
85
+ "entity": "addresses",
86
+ "field": "street",
87
+ "type": "text",
88
+ "rule": "postal-address",
89
+ "category": "location",
90
+ "expected": "street"
91
+ },
92
+ {
93
+ "entity": "addresses",
94
+ "field": "city",
95
+ "type": "text",
96
+ "rule": "postal-address",
97
+ "category": "location",
98
+ "expected": "city"
99
+ },
100
+ {
101
+ "entity": "addresses",
102
+ "field": "postal_code",
103
+ "type": "varchar(16)",
104
+ "rule": "postal-address",
105
+ "category": "location",
106
+ "expected": "zip"
107
+ },
108
+ {
109
+ "entity": "addresses",
110
+ "field": "latitude",
111
+ "type": "numeric(9,6)",
112
+ "rule": "postal-address",
113
+ "category": "location",
114
+ "expected": "geo"
115
+ },
116
+ { "entity": "orders", "field": "id", "type": "integer", "rule": null, "expected": "keep" },
117
+ {
118
+ "entity": "orders",
119
+ "field": "customer_id",
120
+ "type": "integer",
121
+ "rule": null,
122
+ "expected": "keep"
123
+ },
124
+ {
125
+ "entity": "orders",
126
+ "field": "total_cents",
127
+ "type": "integer",
128
+ "rule": null,
129
+ "expected": "keep"
130
+ },
131
+ {
132
+ "entity": "orders",
133
+ "field": "shipping_name",
134
+ "type": "text",
135
+ "rule": "person-name",
136
+ "category": "identity",
137
+ "expected": "full-name"
138
+ },
139
+ {
140
+ "entity": "invoices",
141
+ "field": "billing_email",
142
+ "type": "text",
143
+ "rule": "email",
144
+ "category": "contact",
145
+ "expected": "email"
146
+ },
147
+ {
148
+ "entity": "invoices",
149
+ "field": "billing_name",
150
+ "type": "text",
151
+ "rule": "person-name",
152
+ "category": "identity",
153
+ "expected": "full-name"
154
+ },
155
+ {
156
+ "entity": "invoices",
157
+ "field": "vat_number",
158
+ "type": "varchar(32)",
159
+ "rule": "government-id",
160
+ "category": "government_id",
161
+ "expected": "token"
162
+ },
163
+ {
164
+ "entity": "invoices",
165
+ "field": "amount_cents",
166
+ "type": "integer",
167
+ "rule": null,
168
+ "expected": "keep"
169
+ },
170
+ {
171
+ "entity": "support_tickets",
172
+ "field": "body",
173
+ "type": "text",
174
+ "rule": null,
175
+ "expected": "token"
176
+ },
177
+ {
178
+ "entity": "support_tickets",
179
+ "field": "requester_id",
180
+ "type": "integer",
181
+ "rule": null,
182
+ "expected": "keep"
183
+ },
184
+ {
185
+ "entity": "devices",
186
+ "field": "device_id",
187
+ "type": "uuid",
188
+ "rule": "online-identifier",
189
+ "category": "online_identifier",
190
+ "expected": "uuid"
191
+ },
192
+ {
193
+ "entity": "devices",
194
+ "field": "push_token",
195
+ "type": "text",
196
+ "rule": "online-identifier",
197
+ "category": "online_identifier",
198
+ "expected": "token"
199
+ },
200
+ {
201
+ "entity": "devices",
202
+ "field": "last_seen_ip",
203
+ "type": "inet",
204
+ "rule": "online-identifier",
205
+ "category": "online_identifier",
206
+ "expected": "ip"
207
+ },
208
+ {
209
+ "entity": "profiles",
210
+ "field": "username",
211
+ "type": "text",
212
+ "rule": "username",
213
+ "category": "identity",
214
+ "expected": "username"
215
+ },
216
+ {
217
+ "entity": "profiles",
218
+ "field": "preferences",
219
+ "type": "jsonb",
220
+ "rule": null,
221
+ "expected": "keep"
222
+ }
223
+ ]
224
+ }
package/package.json CHANGED
@@ -1,6 +1,42 @@
1
1
  {
2
2
  "name": "@stonekeep/ai",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.8.0",
4
+ "description": "Stonekeep AI setup assistant with a pre-send privacy guardrail.",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "eval"
18
+ ],
19
+ "engines": {
20
+ "node": ">=20"
21
+ },
22
+ "dependencies": {
23
+ "@anthropic-ai/sdk": "^1.6.0",
24
+ "yaml": "^2.9.1",
25
+ "zod": "^3.25.76",
26
+ "@stonekeep/core": "0.8.0"
27
+ },
28
+ "keywords": [
29
+ "gdpr",
30
+ "rodo",
31
+ "privacy",
32
+ "compliance",
33
+ "poland"
34
+ ],
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "scripts": {
39
+ "build": "tsc -p tsconfig.build.json",
40
+ "eval": "node dist/eval-cli.js"
41
+ }
6
42
  }