norn-cli 2.4.0 → 2.6.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.
Files changed (96) hide show
  1. package/AGENTS.md +2 -2
  2. package/CHANGELOG.md +26 -1
  3. package/dist/cli.js +330 -85
  4. package/package.json +24 -5
  5. package/schemas/norn.config.schema.json +43 -1
  6. package/scripts/__pycache__/reddit_signal_miner.cpython-312.pyc +0 -0
  7. package/scripts/reddit_signal_miner.py +482 -0
  8. package/.claude/settings.local.json +0 -18
  9. package/.claude/skills/norn-social-campaign/SKILL.md +0 -70
  10. package/out/apiResponseIntellisenseCache.js +0 -394
  11. package/out/assertionRunner.js +0 -567
  12. package/out/cacheDir.js +0 -136
  13. package/out/chatParticipant.js +0 -763
  14. package/out/cli/colors.js +0 -127
  15. package/out/cli/formatters/assertion.js +0 -102
  16. package/out/cli/formatters/index.js +0 -23
  17. package/out/cli/formatters/response.js +0 -106
  18. package/out/cli/formatters/summary.js +0 -246
  19. package/out/cli/redaction.js +0 -237
  20. package/out/cli/reporters/html.js +0 -689
  21. package/out/cli/reporters/index.js +0 -22
  22. package/out/cli/reporters/junit.js +0 -226
  23. package/out/codeLensProvider.js +0 -351
  24. package/out/compareContentProvider.js +0 -85
  25. package/out/completionProvider.js +0 -3739
  26. package/out/contractAssertionSummary.js +0 -225
  27. package/out/contractDecorationProvider.js +0 -243
  28. package/out/coverageCalculator.js +0 -879
  29. package/out/coveragePanel.js +0 -597
  30. package/out/debug/breakpointResolver.js +0 -84
  31. package/out/debug/breakpoints.js +0 -52
  32. package/out/debug/nornDebugAdapter.js +0 -166
  33. package/out/debug/nornDebugSession.js +0 -613
  34. package/out/debug/sequenceLocationIndex.js +0 -77
  35. package/out/debug/types.js +0 -3
  36. package/out/deepClone.js +0 -21
  37. package/out/diagnosticProvider.js +0 -2554
  38. package/out/environmentParser.js +0 -736
  39. package/out/environmentProvider.js +0 -544
  40. package/out/environmentTemplates.js +0 -146
  41. package/out/errors/formatError.js +0 -113
  42. package/out/errors/nornError.js +0 -29
  43. package/out/formUrlEncoded.js +0 -89
  44. package/out/httpClient.js +0 -348
  45. package/out/httpRuntimeOptions.js +0 -16
  46. package/out/importErrors.js +0 -31
  47. package/out/inlayHintResolver.js +0 -70
  48. package/out/jsonFileReader.js +0 -323
  49. package/out/mcpClient.js +0 -193
  50. package/out/mcpConfig.js +0 -184
  51. package/out/mcpToolIntellisenseCache.js +0 -96
  52. package/out/mcpToolSchema.js +0 -50
  53. package/out/nornConfig.js +0 -132
  54. package/out/nornHoverProvider.js +0 -124
  55. package/out/nornInlayHintsProvider.js +0 -191
  56. package/out/nornPrompt.js +0 -755
  57. package/out/nornSqlParser.js +0 -286
  58. package/out/nornapiHoverProvider.js +0 -135
  59. package/out/nornapiInlayHintsProvider.js +0 -94
  60. package/out/nornapiParser.js +0 -324
  61. package/out/nornenvCodeActionProvider.js +0 -101
  62. package/out/nornenvDecorationProvider.js +0 -239
  63. package/out/nornenvFoldingProvider.js +0 -63
  64. package/out/nornenvHoverProvider.js +0 -114
  65. package/out/nornenvInlayHintsProvider.js +0 -99
  66. package/out/nornenvLanguageModel.js +0 -187
  67. package/out/nornenvRegionRefactor.js +0 -267
  68. package/out/nornsqlHoverProvider.js +0 -95
  69. package/out/nornsqlInlayHintsProvider.js +0 -114
  70. package/out/parser.js +0 -839
  71. package/out/pathAccess.js +0 -28
  72. package/out/postmanImportPanel.js +0 -732
  73. package/out/postmanImportPlanner.js +0 -1155
  74. package/out/postmanImportSidebarView.js +0 -532
  75. package/out/quotedString.js +0 -35
  76. package/out/requestPreparation.js +0 -179
  77. package/out/requestValidation.js +0 -146
  78. package/out/responsePanel.js +0 -7754
  79. package/out/schemaGenerator.js +0 -562
  80. package/out/scriptRunner.js +0 -419
  81. package/out/secrets/cliSecrets.js +0 -415
  82. package/out/secrets/crypto.js +0 -105
  83. package/out/secrets/envFileSecrets.js +0 -177
  84. package/out/secrets/keyStore.js +0 -259
  85. package/out/sequenceDeclaration.js +0 -15
  86. package/out/sequenceRunner.js +0 -3590
  87. package/out/sqlAdapterRunner.js +0 -122
  88. package/out/sqlBuiltInAdapters.js +0 -604
  89. package/out/sqlConfig.js +0 -184
  90. package/out/starterCatalog.js +0 -554
  91. package/out/stringUtils.js +0 -25
  92. package/out/swaggerBodyIntellisenseCache.js +0 -114
  93. package/out/swaggerParser.js +0 -464
  94. package/out/testProvider.js +0 -767
  95. package/out/theoryCaseLoader.js +0 -113
  96. package/out/validationCache.js +0 -211
@@ -1,70 +0,0 @@
1
- ---
2
- name: norn-social-campaign
3
- description: Continue Peter's Norn LinkedIn social-media campaign — draft/review/schedule posts, reply to engagement, evaluate Week-N kill-criteria, log community signals. Use whenever the user asks about Norn LinkedIn posts, replies, engagement metrics, the GTM experiment, what to post next, or any social/content work for Norn.
4
- ---
5
-
6
- # Norn Social Media Campaign — Cross-Session Continuity
7
-
8
- You are resuming an ongoing GTM experiment Peter has been running for **Norn** (a commercial VS Code extension + CLI he builds solo while employed full-time, UK-based). Most strategic decisions are **locked** — your job is to execute within them, not re-litigate. The canonical state lives in repo `Docs/`; read those before doing real work.
9
-
10
- ## Read these first (canonical, in this repo)
11
-
12
- - `Docs/gtm_plan.md` — **the master plan**. Locked positioning, 13-week kill-criteria with anchored dates, action steps, and an append-only **Progress Log** (scroll to the bottom to see exactly where things stand).
13
- - `Docs/mot_reaction_series.md` — **the LinkedIn posts** (the filename is misnamed — see Anti-patterns; rename pending).
14
- - `Docs/linkedin_series.md` — LinkedIn delivery rules (tags, first-comment text, image policy, posting cadence).
15
- - `Docs/market_signals.md` — append-only community pain-signal intel log. Append; don't re-derive.
16
- - `Docs/postman_rot_essay.md` (long-form v3) and `Docs/mot_series.md` (v1 micro-posts) — backup bank, not active.
17
-
18
- Memory files `norn-gtm-decision.md` and `norn-messaging-locked.md` auto-load and carry the durable decisions.
19
-
20
- ## What is LOCKED — do not reopen
21
-
22
- - **Positioning spine (verbatim, never paraphrase):**
23
- > Norn turns API requests into version-controlled tests your whole team can keep — authored and debugged in VS Code, run on every PR in CI.
24
- Category = repo-native API regression testing. NOT "REST client", NOT vaguer "workflow automation."
25
- - **GTM motion:** public commercial launch (PLG + upmarket founder-led semi-sales). 13-week time-boxed experiment. Kill-criteria + anchored dates in `gtm_plan.md`. "CV-asset" is an accepted non-failure outcome.
26
- - **Sell to:** CTO / founding eng / tech lead at small companies (~≤20–30 people). **QA = evangelist, not buyer.** Eng Manager parked until 50+ people.
27
- - **Pricing model:** VS Code Extension is **free forever (incl. commercial).** CLI is **free for local use.** **Paid only for CLI Pipeline Use (CI/CD).** No evaluation period. Encoded in `LICENSE` (vsApi + Norn, kept in sync).
28
- - **Channel: LinkedIn only.** MoT dropped 2026-05-18. One uniform series posted to LinkedIn.
29
- - **Voice:** dry, specific, recognition-humor, no slang/meme/"touch grass" register, clarity > cleverness, no clever coda after the closing question. ~150–220 words per post.
30
- - **Canonical ending** (append verbatim, URL-free, to every post):
31
- > Disclosure so nobody feels tricked: I build Norn — my attempt at the in-the-repo fix. That's the entire pitch; the post stands without it. Still genuinely curious about the question above, though.
32
- - **Tags (fixed, every post):** `#API #Testing #DeveloperTools`. Add `#MCP` only on the MCP post. 4 max.
33
- - **First comment** (within ~1 min of posting):
34
- > The Norn bit I mentioned, if you want to poke at it: https://nornapi.com
35
- - **Posting cadence:** Tue–Thu ~8:30am UK time, one post/day, ≥4–5 days apart.
36
- - **Visual rule:** real source screenshot (with identifying bits cropped) if a genuine artifact exists; text-only otherwise. **Never** Norn logo. **Never** AI imagery.
37
- - **Self-presentation:** "QA / automation engineer who builds Norn." **Never "Founder."** The reason is strategic (credibility + disclosure-not-pitch posture), not legal — Peter's employment contract is permissive, employer has long known.
38
-
39
- ## Anti-patterns — do NOT do
40
-
41
- - **Don't reopen the positioning spine.** It was rewritten many times before locking; uniformity *is* the strategy.
42
- - **Don't suggest pitching Peter's employer.** Internal-champion path was closed 2026-05-18 — declined "prefer not to sour the relationship" (structural/relationship objection, not product, not IP). Their "no" is excluded from any market-signal read.
43
- - **Don't raise the employer IP-alarm.** Contract permissive; employer long aware; Peter refused a restrictive replacement years ago specifically to protect this. Settled.
44
- - **Don't suggest MoT / Reddit / forums as a posting channel.** Dropped. LinkedIn only.
45
- - **Don't pitch Norn in post bodies or in comment replies.** The canonical ending + first comment do all the selling. Replies = host of a conversation, not salesperson.
46
- - **Don't suggest a separate LinkedIn-native content stream.** One uniform series. Per-channel variants caused the consistency problem the whole session was about.
47
- - **Don't add clever/meta codas after the closing question.** Two prior attempts ("wishful Slack message", "I want to collect these") had to be fixed because they were too clever for the reader. Plain question close, full stop.
48
- - **Don't suggest building product features speculatively.** Build bets in `market_signals.md` are *candidates* — build only after Week-7+ signals justify. "Don't build on spec."
49
- - **Don't write more posts on spec before the Week-4 verdict.** If the format doesn't travel, posts 7+ are wasted on a wrong format.
50
- - **The file `mot_reaction_series.md` is misnamed** (no MoT anymore). Rename to `linkedin_posts.md` is a pending cleanup; cross-refs in `gtm_plan.md` and `linkedin_series.md` will need updating in the same pass.
51
-
52
- ## How to handle common requests
53
-
54
- - **"Give me the next post" / "what should I post":** check the Progress Log in `gtm_plan.md` to see which posts are scheduled vs sent. Posts live in `mot_reaction_series.md`. Format LinkedIn-ready: strip the internal `## N. Title` label; strip markdown asterisks (LinkedIn shows them literally); append the canonical ending; append the tag line; provide the first-comment text separately. Image: real cropped screenshot if a genuine source exists, otherwise text-only.
55
- - **"Reply to this comment":** stay in voice; *extend* the discussion, never thank generically; no Norn pitch in the reply (the bio carries it); reply **fast** — early engagement is the single biggest reach lever, far above any other tactic.
56
- - **"How's the post doing?" / metrics:** calibrate via ratios more than raw numbers. Comment-to-reaction ratio above ~8% = high engagement. A **Send** (LinkedIn DM forward) is the highest-value action — it's the peer-to-peer endorsement the plan was built for. Saves > likes in intent. n=1 is not a trend.
57
- - **"Week-N review":** open the kill-criteria table in `gtm_plan.md` (dates anchored to 2026-05-21 launch). Evaluate signals honestly against the rule. **The pre-committed exit only works if the review actually happens** — sunk cost will narrate; resist it.
58
- - **New community pain signal (Reddit/Lobsters/HN):** append verbatim quote + URL + date to `Docs/market_signals.md`, triage into LinkedIn angle + build-bet columns. Don't let mining displace posting.
59
-
60
- ## Working style with Peter
61
-
62
- - Convert relative dates to absolute when logging (UK timezone).
63
- - Peter is a non-writer; he trusts drafts. When asked for content, give **clean, copy-pasteable** text. No padding, no "here you go" framing.
64
- - Peter values **brevity, decisiveness, and honest pushback** over compliance. If something he's about to do is wrong, say so directly with reasoning. Don't be a yes-man — he's caught me on it before.
65
- - **Consistency is his #1 value.** Keep the Docs as the single source of truth. Any time copy gets revised, sync everywhere it appears.
66
- - When in doubt about state, **read the bottom of `gtm_plan.md`'s Progress Log** — that's where the truth lives.
67
-
68
- ---
69
-
70
- This skill is the index. The `Docs/` files are the canon. When in doubt, read them.
@@ -1,394 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.parseApiResponseRequestLine = parseApiResponseRequestLine;
37
- exports.parseApiResponseRequestText = parseApiResponseRequestText;
38
- exports.saveApiResponseShapeForRequest = saveApiResponseShapeForRequest;
39
- exports.getCachedApiResponseShapeForRequest = getCachedApiResponseShapeForRequest;
40
- exports.getApiResponseShapeNodeAtPath = getApiResponseShapeNodeAtPath;
41
- exports.getApiResponseShapeProperties = getApiResponseShapeProperties;
42
- exports.getApiResponseShapeTypeSummary = getApiResponseShapeTypeSummary;
43
- exports.parseApiResponseBodyPathForCompletion = parseApiResponseBodyPathForCompletion;
44
- const crypto = __importStar(require("crypto"));
45
- const path = __importStar(require("path"));
46
- const cacheDir_1 = require("./cacheDir");
47
- const stringUtils_1 = require("./stringUtils");
48
- const CACHE_VERSION = 1;
49
- const CACHE_FILE = 'api-response-intellisense.json';
50
- const MAX_DEPTH = 6;
51
- const MAX_OBJECT_PROPERTIES = 100;
52
- const MAX_ARRAY_ITEMS = 20;
53
- function removeRetryOptions(value) {
54
- return value
55
- .replace(/\bretry\s+\d+\b/ig, '')
56
- .replace(/\bbackoff\s+\d+(?:\.\d+)?\s*(?:s|ms|seconds?|milliseconds?)?\b/ig, '')
57
- .trim();
58
- }
59
- function normalizeQuotedTarget(target) {
60
- const trimmed = target.trim();
61
- if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
62
- (trimmed.startsWith('\'') && trimmed.endsWith('\''))) {
63
- return trimmed.slice(1, -1).trim();
64
- }
65
- return trimmed;
66
- }
67
- function normalizeTargetForIdentity(target) {
68
- return normalizeQuotedTarget(removeRetryOptions(target)).replace(/\s+/g, ' ').trim();
69
- }
70
- function hashTarget(target) {
71
- return crypto.createHash('sha256').update(target).digest('hex').slice(0, 16);
72
- }
73
- function redactSensitiveTargetParts(target) {
74
- let redacted = target.replace(/(https?:\/\/)([^/\s:@]+):([^/\s@]+)@/ig, '$1***@');
75
- redacted = redacted.replace(/([?&][^=&\s]*(?:token|secret|password|passwd|pwd|key|auth|signature|sig)[^=&\s]*=)[^&\s]+/ig, '$1***');
76
- redacted = redacted.replace(/\b(?:bearer|basic)\s+[a-z0-9._~+/=-]+/ig, match => `${match.split(/\s+/)[0]} ***`);
77
- return redacted;
78
- }
79
- function normalizeEnvironment(environment) {
80
- const trimmed = environment?.trim();
81
- return trimmed ? trimmed : undefined;
82
- }
83
- function normalizeIdentity(identity) {
84
- const rootPath = (0, cacheDir_1.findProjectRoot)(identity.sourceFile);
85
- const absoluteSourceFile = path.resolve(identity.sourceFile);
86
- const relativeSourceFile = path.relative(rootPath, absoluteSourceFile).replace(/\\/g, '/');
87
- const target = normalizeTargetForIdentity(identity.target);
88
- return {
89
- rootPath,
90
- sourceFile: relativeSourceFile || path.basename(absoluteSourceFile),
91
- sourceLine: identity.sourceLine,
92
- method: identity.method.toUpperCase(),
93
- target,
94
- targetHash: hashTarget(target),
95
- environment: normalizeEnvironment(identity.environment)
96
- };
97
- }
98
- function buildCacheKey(identity) {
99
- return [
100
- identity.sourceFile,
101
- identity.sourceLine === undefined ? '' : String(identity.sourceLine),
102
- identity.method,
103
- identity.targetHash,
104
- identity.environment ?? ''
105
- ].map(encodeURIComponent).join('|');
106
- }
107
- function getCachePath(rootPath) {
108
- return (0, cacheDir_1.getNornCacheFilePath)(rootPath, CACHE_FILE);
109
- }
110
- function loadCache(rootPath) {
111
- return (0, cacheDir_1.loadVersionedJsonCache)({
112
- cachePath: getCachePath(rootPath),
113
- version: CACHE_VERSION,
114
- createDefault: () => ({ version: CACHE_VERSION, entries: {} }),
115
- isValid: cache => typeof cache.entries === 'object' && cache.entries !== null
116
- });
117
- }
118
- function saveCache(rootPath, cache) {
119
- return (0, cacheDir_1.saveVersionedJsonCache)(getCachePath(rootPath), cache, () => !!(0, cacheDir_1.ensureNornCacheDir)(rootPath));
120
- }
121
- function parseApiResponseRequestLine(line) {
122
- const trimmed = (0, stringUtils_1.stripInlineComment)(line).trim();
123
- const varMatch = trimmed.match(/^var\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+(.+)$/i);
124
- if (varMatch) {
125
- return {
126
- variableName: varMatch[1],
127
- method: varMatch[2].toUpperCase(),
128
- target: normalizeTargetForIdentity(varMatch[3])
129
- };
130
- }
131
- const requestMatch = trimmed.match(/^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+(.+)$/i);
132
- if (!requestMatch) {
133
- return undefined;
134
- }
135
- return {
136
- method: requestMatch[1].toUpperCase(),
137
- target: normalizeTargetForIdentity(requestMatch[2])
138
- };
139
- }
140
- function parseApiResponseRequestText(requestText) {
141
- const lines = requestText.split('\n');
142
- for (let lineOffset = 0; lineOffset < lines.length; lineOffset++) {
143
- const request = parseApiResponseRequestLine(lines[lineOffset]);
144
- if (request) {
145
- return { lineOffset, request };
146
- }
147
- }
148
- return undefined;
149
- }
150
- function getCacheableJsonRoot(responseBody) {
151
- let candidate = responseBody;
152
- if (typeof candidate === 'string') {
153
- try {
154
- candidate = JSON.parse(candidate);
155
- }
156
- catch {
157
- return undefined;
158
- }
159
- }
160
- if (candidate !== null && typeof candidate === 'object') {
161
- return candidate;
162
- }
163
- return undefined;
164
- }
165
- function getPrimitiveShape(value) {
166
- if (value === null) {
167
- return { type: 'null' };
168
- }
169
- switch (typeof value) {
170
- case 'string':
171
- return { type: 'string' };
172
- case 'number':
173
- return { type: 'number' };
174
- case 'boolean':
175
- return { type: 'boolean' };
176
- default:
177
- return { type: 'unknown' };
178
- }
179
- }
180
- function buildShape(value, depth = 0) {
181
- if (depth >= MAX_DEPTH) {
182
- if (Array.isArray(value)) {
183
- return { type: 'array', items: { type: 'unknown' } };
184
- }
185
- if (value !== null && typeof value === 'object') {
186
- return { type: 'object', properties: {} };
187
- }
188
- return getPrimitiveShape(value);
189
- }
190
- if (Array.isArray(value)) {
191
- const itemShapes = value
192
- .slice(0, MAX_ARRAY_ITEMS)
193
- .map(item => buildShape(item, depth + 1));
194
- return {
195
- type: 'array',
196
- items: mergeShapes(itemShapes)
197
- };
198
- }
199
- if (value !== null && typeof value === 'object') {
200
- const properties = {};
201
- const entries = Object.entries(value).slice(0, MAX_OBJECT_PROPERTIES);
202
- for (const [name, propertyValue] of entries) {
203
- properties[name] = buildShape(propertyValue, depth + 1);
204
- }
205
- return { type: 'object', properties };
206
- }
207
- return getPrimitiveShape(value);
208
- }
209
- function mergeObjectShapes(nodes) {
210
- const propertyNames = new Set();
211
- for (const node of nodes) {
212
- for (const name of Object.keys(node.properties ?? {})) {
213
- propertyNames.add(name);
214
- }
215
- }
216
- const properties = {};
217
- for (const name of [...propertyNames].slice(0, MAX_OBJECT_PROPERTIES)) {
218
- const propertyShapes = nodes
219
- .map(node => node.properties?.[name])
220
- .filter((node) => Boolean(node));
221
- properties[name] = mergeShapes(propertyShapes);
222
- }
223
- return { type: 'object', properties };
224
- }
225
- function mergeArrayShapes(nodes) {
226
- return {
227
- type: 'array',
228
- items: mergeShapes(nodes
229
- .map(node => node.items)
230
- .filter((node) => Boolean(node)))
231
- };
232
- }
233
- function mergeShapes(nodes) {
234
- if (nodes.length === 0) {
235
- return { type: 'unknown' };
236
- }
237
- const hasNull = nodes.some(node => node.type === 'null');
238
- const nonNullNodes = nodes.filter(node => node.type !== 'null');
239
- const effectiveNodes = nonNullNodes.length > 0 ? nonNullNodes : nodes;
240
- const firstType = effectiveNodes[0].type;
241
- const sameType = effectiveNodes.every(node => node.type === firstType);
242
- if (!sameType) {
243
- return { type: 'unknown', nullable: hasNull || undefined };
244
- }
245
- let merged;
246
- if (firstType === 'object') {
247
- merged = mergeObjectShapes(effectiveNodes);
248
- }
249
- else if (firstType === 'array') {
250
- merged = mergeArrayShapes(effectiveNodes);
251
- }
252
- else {
253
- merged = { type: firstType };
254
- }
255
- if (hasNull && merged.type !== 'null') {
256
- merged.nullable = true;
257
- }
258
- return merged;
259
- }
260
- function saveApiResponseShapeForRequest(identity, responseBody) {
261
- const root = getCacheableJsonRoot(responseBody);
262
- if (root === undefined) {
263
- return false;
264
- }
265
- const shape = buildShape(root);
266
- if (shape.type !== 'object' && shape.type !== 'array') {
267
- return false;
268
- }
269
- const normalizedIdentity = normalizeIdentity(identity);
270
- const cache = loadCache(normalizedIdentity.rootPath);
271
- const key = buildCacheKey(normalizedIdentity);
272
- cache.entries[key] = {
273
- method: normalizedIdentity.method,
274
- target: redactSensitiveTargetParts(normalizedIdentity.target),
275
- targetHash: normalizedIdentity.targetHash,
276
- sourceFile: normalizedIdentity.sourceFile,
277
- sourceLine: normalizedIdentity.sourceLine,
278
- environment: normalizedIdentity.environment,
279
- cachedAt: new Date().toISOString(),
280
- shape
281
- };
282
- return saveCache(normalizedIdentity.rootPath, cache);
283
- }
284
- function findBestEntry(entries, identity) {
285
- const exactLineEntries = entries.filter(entry => entry.sourceLine === identity.sourceLine);
286
- const linePreferredEntries = exactLineEntries.length > 0 ? exactLineEntries : entries;
287
- const exactEnvironment = identity.environment
288
- ? linePreferredEntries.find(entry => entry.environment === identity.environment)
289
- : undefined;
290
- if (exactEnvironment) {
291
- return exactEnvironment;
292
- }
293
- const noEnvironment = linePreferredEntries.find(entry => !entry.environment);
294
- if (noEnvironment) {
295
- return noEnvironment;
296
- }
297
- return linePreferredEntries
298
- .slice()
299
- .sort((a, b) => b.cachedAt.localeCompare(a.cachedAt))[0];
300
- }
301
- function getCachedApiResponseShapeForRequest(identity) {
302
- const normalizedIdentity = normalizeIdentity(identity);
303
- const cache = loadCache(normalizedIdentity.rootPath);
304
- const exactKey = buildCacheKey(normalizedIdentity);
305
- const exactEntry = cache.entries[exactKey];
306
- if (exactEntry) {
307
- return exactEntry;
308
- }
309
- const candidates = Object.values(cache.entries).filter(entry => entry.sourceFile === normalizedIdentity.sourceFile &&
310
- entry.method === normalizedIdentity.method &&
311
- entry.targetHash === normalizedIdentity.targetHash);
312
- return findBestEntry(candidates, normalizedIdentity);
313
- }
314
- function isNumericSegment(segment) {
315
- return /^\d+$/.test(segment);
316
- }
317
- function getApiResponseShapeNodeAtPath(shape, pathSegments) {
318
- let current = shape;
319
- for (const segment of pathSegments) {
320
- if (!current) {
321
- return undefined;
322
- }
323
- if (current.type === 'array') {
324
- current = current.items;
325
- if (isNumericSegment(segment)) {
326
- continue;
327
- }
328
- }
329
- if (current?.type !== 'object') {
330
- return undefined;
331
- }
332
- current = current.properties?.[segment];
333
- }
334
- return current;
335
- }
336
- function isPathSafePropertyName(name) {
337
- return /^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(name);
338
- }
339
- function getApiResponseShapeProperties(node) {
340
- if (!node) {
341
- return [];
342
- }
343
- if (node.type === 'array') {
344
- const itemProperties = getApiResponseShapeProperties(node.items);
345
- return [
346
- { name: 'count', shape: { type: 'number' } },
347
- ...itemProperties
348
- ];
349
- }
350
- if (node.type !== 'object' || !node.properties) {
351
- return [];
352
- }
353
- return Object.entries(node.properties)
354
- .filter(([name]) => isPathSafePropertyName(name))
355
- .map(([name, shape]) => ({ name, shape }))
356
- .sort((a, b) => a.name.localeCompare(b.name));
357
- }
358
- function getApiResponseShapeTypeSummary(node) {
359
- let summary;
360
- if (node.type === 'array') {
361
- const itemSummary = node.items ? getApiResponseShapeTypeSummary(node.items) : 'unknown';
362
- summary = `array<${itemSummary}>`;
363
- }
364
- else {
365
- summary = node.type;
366
- }
367
- return node.nullable ? `${summary} | null` : summary;
368
- }
369
- function parseApiResponseBodyPathForCompletion(suffix) {
370
- const pathSuffix = suffix.startsWith('[') ? `.${suffix}` : suffix;
371
- if (!pathSuffix.startsWith('.')) {
372
- return undefined;
373
- }
374
- const normalized = pathSuffix.replace(/\[(\d+)\]/g, '.$1');
375
- if (!normalized.startsWith('.')) {
376
- return undefined;
377
- }
378
- const withoutLeadingDot = normalized.slice(1);
379
- const endsAtSeparator = normalized.endsWith('.');
380
- const rawParts = withoutLeadingDot.split('.');
381
- const nonEmptyParts = rawParts.filter(part => part !== '');
382
- if (endsAtSeparator) {
383
- return {
384
- pathSegments: nonEmptyParts,
385
- partial: ''
386
- };
387
- }
388
- const partial = nonEmptyParts.at(-1) ?? '';
389
- return {
390
- pathSegments: nonEmptyParts.slice(0, -1),
391
- partial
392
- };
393
- }
394
- //# sourceMappingURL=apiResponseIntellisenseCache.js.map