norn-cli 2.2.2 → 2.4.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 (113) hide show
  1. package/.claude/settings.local.json +18 -0
  2. package/.claude/skills/norn-social-campaign/SKILL.md +70 -0
  3. package/CHANGELOG.md +22 -1
  4. package/LICENSE +20 -29
  5. package/README.md +32 -1
  6. package/demos/nornenv-region-refactor/README.md +64 -0
  7. package/demos/nornenv-showcase/README.md +62 -0
  8. package/demos/nornenv-showcase/norn.config.json +16 -0
  9. package/demos/nornenv-showcase/showcase.norn +70 -0
  10. package/demos/nornenv-showcase/showcase.nornapi +26 -0
  11. package/demos/nornenv-showcase/showcase.nornsql +20 -0
  12. package/dist/cli.js +564 -54
  13. package/out/apiResponseIntellisenseCache.js +394 -0
  14. package/out/assertionRunner.js +567 -0
  15. package/out/cacheDir.js +136 -0
  16. package/out/chatParticipant.js +763 -0
  17. package/out/cli/colors.js +127 -0
  18. package/out/cli/formatters/assertion.js +102 -0
  19. package/out/cli/formatters/index.js +23 -0
  20. package/out/cli/formatters/response.js +106 -0
  21. package/out/cli/formatters/summary.js +246 -0
  22. package/out/cli/redaction.js +237 -0
  23. package/out/cli/reporters/html.js +689 -0
  24. package/out/cli/reporters/index.js +22 -0
  25. package/out/cli/reporters/junit.js +226 -0
  26. package/out/codeLensProvider.js +351 -0
  27. package/out/compareContentProvider.js +85 -0
  28. package/out/completionProvider.js +3739 -0
  29. package/out/contractAssertionSummary.js +225 -0
  30. package/out/contractDecorationProvider.js +243 -0
  31. package/out/coverageCalculator.js +879 -0
  32. package/out/coveragePanel.js +597 -0
  33. package/out/debug/breakpointResolver.js +84 -0
  34. package/out/debug/breakpoints.js +52 -0
  35. package/out/debug/nornDebugAdapter.js +166 -0
  36. package/out/debug/nornDebugSession.js +613 -0
  37. package/out/debug/sequenceLocationIndex.js +77 -0
  38. package/out/debug/types.js +3 -0
  39. package/out/deepClone.js +21 -0
  40. package/out/diagnosticProvider.js +2554 -0
  41. package/out/environmentParser.js +736 -0
  42. package/out/environmentProvider.js +544 -0
  43. package/out/environmentTemplates.js +146 -0
  44. package/out/errors/formatError.js +113 -0
  45. package/out/errors/nornError.js +29 -0
  46. package/out/formUrlEncoded.js +89 -0
  47. package/out/httpClient.js +348 -0
  48. package/out/httpRuntimeOptions.js +16 -0
  49. package/out/importErrors.js +31 -0
  50. package/out/inlayHintResolver.js +70 -0
  51. package/out/jsonFileReader.js +323 -0
  52. package/out/mcpClient.js +193 -0
  53. package/out/mcpConfig.js +184 -0
  54. package/out/mcpToolIntellisenseCache.js +96 -0
  55. package/out/mcpToolSchema.js +50 -0
  56. package/out/nornConfig.js +132 -0
  57. package/out/nornHoverProvider.js +124 -0
  58. package/out/nornInlayHintsProvider.js +191 -0
  59. package/out/nornPrompt.js +755 -0
  60. package/out/nornSqlParser.js +286 -0
  61. package/out/nornapiHoverProvider.js +135 -0
  62. package/out/nornapiInlayHintsProvider.js +94 -0
  63. package/out/nornapiParser.js +324 -0
  64. package/out/nornenvCodeActionProvider.js +101 -0
  65. package/out/nornenvDecorationProvider.js +239 -0
  66. package/out/nornenvFoldingProvider.js +63 -0
  67. package/out/nornenvHoverProvider.js +114 -0
  68. package/out/nornenvInlayHintsProvider.js +99 -0
  69. package/out/nornenvLanguageModel.js +187 -0
  70. package/out/nornenvRegionRefactor.js +267 -0
  71. package/out/nornsqlHoverProvider.js +95 -0
  72. package/out/nornsqlInlayHintsProvider.js +114 -0
  73. package/out/parser.js +839 -0
  74. package/out/pathAccess.js +28 -0
  75. package/out/postmanImportPanel.js +732 -0
  76. package/out/postmanImportPlanner.js +1155 -0
  77. package/out/postmanImportSidebarView.js +532 -0
  78. package/out/quotedString.js +35 -0
  79. package/out/requestPreparation.js +179 -0
  80. package/out/requestValidation.js +146 -0
  81. package/out/responsePanel.js +7754 -0
  82. package/out/schemaGenerator.js +562 -0
  83. package/out/scriptRunner.js +419 -0
  84. package/out/secrets/cliSecrets.js +415 -0
  85. package/out/secrets/crypto.js +105 -0
  86. package/out/secrets/envFileSecrets.js +177 -0
  87. package/out/secrets/keyStore.js +259 -0
  88. package/out/sequenceDeclaration.js +15 -0
  89. package/out/sequenceRunner.js +3590 -0
  90. package/out/sqlAdapterRunner.js +122 -0
  91. package/out/sqlBuiltInAdapters.js +604 -0
  92. package/out/sqlConfig.js +184 -0
  93. package/out/starterCatalog.js +554 -0
  94. package/out/stringUtils.js +25 -0
  95. package/out/swaggerBodyIntellisenseCache.js +114 -0
  96. package/out/swaggerParser.js +464 -0
  97. package/out/testProvider.js +767 -0
  98. package/out/theoryCaseLoader.js +113 -0
  99. package/out/validationCache.js +211 -0
  100. package/package.json +38 -11
  101. package/.kanbn/index.md +0 -31
  102. package/.kanbn/tasks/book-first-mentor-session.md +0 -13
  103. package/.kanbn/tasks/decide-what-success-in-a-pilot-looks-like.md +0 -9
  104. package/.kanbn/tasks/do-5-customer-conversations.md +0 -9
  105. package/.kanbn/tasks/finalise-the-one-line-pitch.md +0 -11
  106. package/.kanbn/tasks/interview-script.md +0 -49
  107. package/.kanbn/tasks/make-a-list-of-10-people-to-speak-to.md +0 -11
  108. package/.kanbn/tasks/prepare-your-customer-interview-questions.md +0 -11
  109. package/.kanbn/tasks/recruit-2/342/200/2233-pilot-users.md +0 -9
  110. package/.kanbn/tasks/refine-your-pitch.md +0 -9
  111. package/.kanbn/tasks/use-the-shiplight-website-as-a-template-to-improve-norn-website.md +0 -9
  112. package/.kanbn/tasks/write-down-repeated-wording.md +0 -9
  113. package/.kanbn/tasks/write-the-one-pager.md +0 -27
@@ -0,0 +1,113 @@
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.formatTheoryCaseLabel = formatTheoryCaseLabel;
37
+ exports.normalizeTheoryCase = normalizeTheoryCase;
38
+ exports.loadTheoryCasesFromSource = loadTheoryCasesFromSource;
39
+ exports.resolveTheoryCases = resolveTheoryCases;
40
+ exports.buildTheoryCaseArgs = buildTheoryCaseArgs;
41
+ exports.resolveSequenceRunCases = resolveSequenceRunCases;
42
+ const fs = __importStar(require("fs/promises"));
43
+ const path = __importStar(require("path"));
44
+ function formatTheoryCaseLabel(params, index) {
45
+ const entries = Object.entries(params);
46
+ if (entries.length === 0) {
47
+ return index === undefined ? '[]' : `Case ${index + 1}`;
48
+ }
49
+ const parts = entries.map(([key, value]) => {
50
+ if (typeof value === 'string') {
51
+ return `${key}="${value}"`;
52
+ }
53
+ return `${key}=${value}`;
54
+ });
55
+ return `[${parts.join(', ')}]`;
56
+ }
57
+ function normalizeTheoryCase(caseValue, sequenceParamNames) {
58
+ if (caseValue && typeof caseValue === 'object' && !Array.isArray(caseValue)) {
59
+ return caseValue;
60
+ }
61
+ if (sequenceParamNames.length === 1) {
62
+ return { [sequenceParamNames[0]]: caseValue };
63
+ }
64
+ return undefined;
65
+ }
66
+ async function loadTheoryCasesFromSource(sourcePath, workingDir, sequenceParamNames) {
67
+ const resolvedPath = path.resolve(workingDir, sourcePath);
68
+ const content = await fs.readFile(resolvedPath, 'utf-8');
69
+ const parsed = JSON.parse(content);
70
+ if (!Array.isArray(parsed)) {
71
+ throw new Error(`Theory file "${sourcePath}" must contain a JSON array of test cases`);
72
+ }
73
+ const cases = [];
74
+ for (let index = 0; index < parsed.length; index++) {
75
+ const normalized = normalizeTheoryCase(parsed[index], sequenceParamNames);
76
+ if (!normalized) {
77
+ throw new Error(`Theory file "${sourcePath}" has invalid case at index ${index}. ` +
78
+ 'Use objects for multi-parameter sequences.');
79
+ }
80
+ cases.push(normalized);
81
+ }
82
+ return cases;
83
+ }
84
+ async function resolveTheoryCases(sequence, workingDir) {
85
+ const inlineCases = sequence.theoryData?.cases ?? [];
86
+ if (inlineCases.length > 0) {
87
+ return inlineCases;
88
+ }
89
+ if (!sequence.theoryData?.source) {
90
+ return [];
91
+ }
92
+ return loadTheoryCasesFromSource(sequence.theoryData.source, workingDir, sequence.parameters.map(parameter => parameter.name));
93
+ }
94
+ function buildTheoryCaseArgs(defaultArgs, caseParams) {
95
+ const args = { ...defaultArgs };
96
+ for (const [key, value] of Object.entries(caseParams)) {
97
+ if (value !== undefined) {
98
+ args[key] = String(value);
99
+ }
100
+ }
101
+ return args;
102
+ }
103
+ async function resolveSequenceRunCases(sequence, workingDir, defaultArgs) {
104
+ const cases = await resolveTheoryCases(sequence, workingDir);
105
+ if (cases.length === 0) {
106
+ return [{ args: { ...defaultArgs } }];
107
+ }
108
+ return cases.map((caseParams, index) => ({
109
+ label: formatTheoryCaseLabel(caseParams, index),
110
+ args: buildTheoryCaseArgs(defaultArgs, caseParams)
111
+ }));
112
+ }
113
+ //# sourceMappingURL=theoryCaseLoader.js.map
@@ -0,0 +1,211 @@
1
+ "use strict";
2
+ /**
3
+ * Validation Cache - Stores schema validation results for decorations and status tracking
4
+ * Results are persisted in .norn-cache/validation-results.json
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.loadCache = loadCache;
41
+ exports.saveValidationResult = saveValidationResult;
42
+ exports.getResultForAssertion = getResultForAssertion;
43
+ exports.getResultsForFile = getResultsForFile;
44
+ exports.clearResultsForFile = clearResultsForFile;
45
+ exports.clearAllResults = clearAllResults;
46
+ exports.markResultsAsStale = markResultsAsStale;
47
+ const vscode = __importStar(require("vscode"));
48
+ const cacheDir_1 = require("./cacheDir");
49
+ const CACHE_VERSION = 1;
50
+ const CACHE_FILE = 'validation-results.json';
51
+ /**
52
+ * Get the workspace root folder
53
+ */
54
+ function getWorkspaceRoot() {
55
+ const workspaceFolders = vscode.workspace.workspaceFolders;
56
+ if (workspaceFolders && workspaceFolders.length > 0) {
57
+ return workspaceFolders[0].uri.fsPath;
58
+ }
59
+ return undefined;
60
+ }
61
+ /**
62
+ * Get the path to the cache file
63
+ */
64
+ function getCachePath() {
65
+ const root = getWorkspaceRoot();
66
+ if (!root) {
67
+ return undefined;
68
+ }
69
+ return (0, cacheDir_1.getNornCacheFilePath)(root, CACHE_FILE);
70
+ }
71
+ /**
72
+ * Ensure the cache directory exists
73
+ */
74
+ function ensureCacheDir() {
75
+ const root = getWorkspaceRoot();
76
+ if (!root) {
77
+ return false;
78
+ }
79
+ return !!(0, cacheDir_1.ensureNornCacheDir)(root);
80
+ }
81
+ /**
82
+ * Load the validation cache from disk
83
+ */
84
+ function loadCache() {
85
+ return (0, cacheDir_1.loadVersionedJsonCache)({
86
+ cachePath: getCachePath(),
87
+ version: CACHE_VERSION,
88
+ createDefault: () => ({ version: CACHE_VERSION, results: {} }),
89
+ isValid: cache => typeof cache.results === 'object' && cache.results !== null
90
+ });
91
+ }
92
+ /**
93
+ * Save the cache to disk
94
+ */
95
+ function saveCache(cache) {
96
+ return (0, cacheDir_1.saveVersionedJsonCache)(getCachePath(), cache, ensureCacheDir);
97
+ }
98
+ /**
99
+ * Generate a cache key from source file and line number
100
+ */
101
+ function getCacheKey(sourceFile, line) {
102
+ // Normalize to workspace-relative path
103
+ const root = getWorkspaceRoot();
104
+ let relativePath = sourceFile;
105
+ if (root && sourceFile.startsWith(root)) {
106
+ relativePath = sourceFile.slice(root.length + 1);
107
+ }
108
+ return `${relativePath}:${line}`;
109
+ }
110
+ /**
111
+ * Save a validation result to the cache
112
+ */
113
+ function saveValidationResult(result) {
114
+ const cache = loadCache();
115
+ const key = getCacheKey(result.sourceFile, result.assertionLine);
116
+ // Normalize paths
117
+ const root = getWorkspaceRoot();
118
+ if (root) {
119
+ if (result.sourceFile.startsWith(root)) {
120
+ result.sourceFile = result.sourceFile.slice(root.length + 1);
121
+ }
122
+ if (result.schemaPath.startsWith(root)) {
123
+ result.schemaPath = result.schemaPath.slice(root.length + 1);
124
+ }
125
+ }
126
+ cache.results[key] = result;
127
+ saveCache(cache);
128
+ }
129
+ /**
130
+ * Get validation result for a specific assertion
131
+ * @param sourceFile Absolute path to the .norn file
132
+ * @param line Line number (0-based)
133
+ */
134
+ function getResultForAssertion(sourceFile, line) {
135
+ const cache = loadCache();
136
+ const key = getCacheKey(sourceFile, line);
137
+ return cache.results[key];
138
+ }
139
+ /**
140
+ * Get all validation results for a source file
141
+ * @param sourceFile Absolute path to the .norn file
142
+ */
143
+ function getResultsForFile(sourceFile) {
144
+ const cache = loadCache();
145
+ const results = [];
146
+ // Normalize path for comparison
147
+ const root = getWorkspaceRoot();
148
+ let normalizedPath = sourceFile;
149
+ if (root && sourceFile.startsWith(root)) {
150
+ normalizedPath = sourceFile.slice(root.length + 1);
151
+ }
152
+ for (const [key, result] of Object.entries(cache.results)) {
153
+ const [filePath] = key.split(':');
154
+ if (filePath === normalizedPath) {
155
+ results.push(result);
156
+ }
157
+ }
158
+ return results;
159
+ }
160
+ /**
161
+ * Clear all cached results for a specific file
162
+ */
163
+ function clearResultsForFile(sourceFile) {
164
+ const cache = loadCache();
165
+ // Normalize path for comparison
166
+ const root = getWorkspaceRoot();
167
+ let normalizedPath = sourceFile;
168
+ if (root && sourceFile.startsWith(root)) {
169
+ normalizedPath = sourceFile.slice(root.length + 1);
170
+ }
171
+ const keysToDelete = [];
172
+ for (const key of Object.keys(cache.results)) {
173
+ const [filePath] = key.split(':');
174
+ if (filePath === normalizedPath) {
175
+ keysToDelete.push(key);
176
+ }
177
+ }
178
+ keysToDelete.forEach(key => delete cache.results[key]);
179
+ saveCache(cache);
180
+ }
181
+ /**
182
+ * Clear all cached results
183
+ */
184
+ function clearAllResults() {
185
+ const cache = { version: CACHE_VERSION, results: {} };
186
+ saveCache(cache);
187
+ }
188
+ /**
189
+ * Mark all results for a file as stale (used when file changes)
190
+ */
191
+ function markResultsAsStale(sourceFile) {
192
+ const cache = loadCache();
193
+ // Normalize path for comparison
194
+ const root = getWorkspaceRoot();
195
+ let normalizedPath = sourceFile;
196
+ if (root && sourceFile.startsWith(root)) {
197
+ normalizedPath = sourceFile.slice(root.length + 1);
198
+ }
199
+ let modified = false;
200
+ for (const [key, result] of Object.entries(cache.results)) {
201
+ const [filePath] = key.split(':');
202
+ if (filePath === normalizedPath && result.status !== 'stale') {
203
+ result.status = 'stale';
204
+ modified = true;
205
+ }
206
+ }
207
+ if (modified) {
208
+ saveCache(cache);
209
+ }
210
+ }
211
+ //# sourceMappingURL=validationCache.js.map
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "norn-cli",
3
- "displayName": "Norn - REST Client",
4
- "description": "A powerful REST client for making HTTP requests with sequences, variables, scripts, and cookie support",
5
- "version": "2.2.2",
3
+ "displayName": "Norn API Tests in Your Repo",
4
+ "description": "Version-controlled API tests your team can keep. Author and debug HTTP sequences in VS Code, then run the same files in CI.",
5
+ "version": "2.4.0",
6
6
  "publisher": "Norn-PeterKrustanov",
7
7
  "author": {
8
8
  "name": "Peter Krastanov"
@@ -22,21 +22,22 @@
22
22
  "theme": "dark"
23
23
  },
24
24
  "keywords": [
25
- "rest",
26
- "api",
25
+ "api testing",
26
+ "integration testing",
27
+ "ci",
28
+ "regression testing",
29
+ "api automation",
30
+ "openapi",
27
31
  "http",
28
- "request",
29
- "client",
30
- "rest client",
31
- "api testing"
32
+ "test automation"
32
33
  ],
33
34
  "engines": {
34
35
  "vscode": "^1.108.1"
35
36
  },
36
37
  "categories": [
38
+ "Testing",
37
39
  "Programming Languages",
38
- "Snippets",
39
- "Other"
40
+ "Snippets"
40
41
  ],
41
42
  "activationEvents": [
42
43
  "onLanguage:norn",
@@ -87,6 +88,26 @@
87
88
  "title": "Select Environment",
88
89
  "category": "Norn"
89
90
  },
91
+ {
92
+ "command": "norn.nornenv.activate",
93
+ "title": "Activate Norn Environment",
94
+ "category": "Norn"
95
+ },
96
+ {
97
+ "command": "norn.nornenv.deactivate",
98
+ "title": "Deactivate Norn Environment",
99
+ "category": "Norn"
100
+ },
101
+ {
102
+ "command": "norn.nornenv.peekInherited",
103
+ "title": "Peek Inherited Norn Environment Variables",
104
+ "category": "Norn"
105
+ },
106
+ {
107
+ "command": "norn.nornenv.refactorRegionPattern",
108
+ "title": "Refactor Region Pattern To Templates",
109
+ "category": "Norn"
110
+ },
90
111
  {
91
112
  "command": "norn.showCoverage",
92
113
  "title": "Show API Coverage",
@@ -344,6 +365,12 @@
344
365
  "key": "ctr+alt+r",
345
366
  "mac": "ctr+alt+r",
346
367
  "when": "editorLangId == norn"
368
+ },
369
+ {
370
+ "command": "norn.nornenv.enter",
371
+ "key": "enter",
372
+ "mac": "enter",
373
+ "when": "editorTextFocus && editorLangId == nornenv && !suggestWidgetVisible"
347
374
  }
348
375
  ],
349
376
  "chatParticipants": [
package/.kanbn/index.md DELETED
@@ -1,31 +0,0 @@
1
- ---
2
- startedColumns:
3
- - 'In Progress'
4
- completedColumns:
5
- - Done
6
- ---
7
-
8
- # Norn
9
-
10
- ## Backlog
11
-
12
- - [do-5-customer-conversations](tasks/do-5-customer-conversations.md)
13
- - [write-down-repeated-wording](tasks/write-down-repeated-wording.md)
14
- - [refine-your-pitch](tasks/refine-your-pitch.md)
15
- - [recruit-2–3-pilot-users](tasks/recruit-2–3-pilot-users.md)
16
- - [decide-what-success-in-a-pilot-looks-like](tasks/decide-what-success-in-a-pilot-looks-like.md)
17
- - [interview-script](tasks/interview-script.md)
18
-
19
- ## Todo
20
-
21
- - [finalise-the-one-line-pitch](tasks/finalise-the-one-line-pitch.md)
22
- - [make-a-list-of-10-people-to-speak-to](tasks/make-a-list-of-10-people-to-speak-to.md)
23
- - [prepare-your-customer-interview-questions](tasks/prepare-your-customer-interview-questions.md)
24
- - [use-the-shiplight-website-as-a-template-to-improve-norn-website](tasks/use-the-shiplight-website-as-a-template-to-improve-norn-website.md)
25
-
26
- ## In Progress
27
-
28
- ## Done
29
-
30
- - [book-first-mentor-session](tasks/book-first-mentor-session.md)
31
- - [write-the-one-pager](tasks/write-the-one-pager.md)
@@ -1,13 +0,0 @@
1
- ---
2
- created: 2026-04-09T22:55:34.350Z
3
- updated: 2026-04-12T12:19:50.345Z
4
- assigned: ""
5
- progress: 0
6
- tags:
7
- - 'Week One'
8
- due: 2026-04-12T00:00:00.000Z
9
- started: 2026-04-12T09:08:33.269Z
10
- completed: 2026-04-12T12:19:50.345Z
11
- ---
12
-
13
- # Book first mentor session
@@ -1,9 +0,0 @@
1
- ---
2
- created: 2026-04-09T23:04:43.339Z
3
- updated: 2026-04-09T23:04:43.337Z
4
- assigned: ""
5
- progress: 0
6
- tags: []
7
- ---
8
-
9
- # Decide what success in a pilot looks like
@@ -1,9 +0,0 @@
1
- ---
2
- created: 2026-04-09T23:03:31.570Z
3
- updated: 2026-04-09T23:03:31.569Z
4
- assigned: ""
5
- progress: 0
6
- tags: []
7
- ---
8
-
9
- # Do 5 customer conversations
@@ -1,11 +0,0 @@
1
- ---
2
- created: 2026-04-09T22:59:46.277Z
3
- updated: 2026-04-09T23:03:00.808Z
4
- assigned: ""
5
- progress: 0
6
- tags:
7
- - 'Week One'
8
- due: 2026-04-12T00:00:00.000Z
9
- ---
10
-
11
- # Finalise the one-line pitch
@@ -1,49 +0,0 @@
1
- ---
2
- created: 2026-04-09T23:25:42.568Z
3
- updated: 2026-04-09T23:25:42.565Z
4
- assigned: ""
5
- progress: 0
6
- tags: []
7
- ---
8
-
9
- # Interview Script
10
-
11
- This is the important bit. You are not trying to impress them. You are trying to learn where the pain is.
12
- Opening
13
- “Thanks for taking the time. I’m working on a VS Code-native API testing and automation tool called Norn. I’m speaking to people to understand how teams currently handle API testing, bug reproduction, and automation. I’m not here to hard-sell you anything — I mainly want to learn how you do things today and where the pain is.”
14
- Warm-up
15
- “Can you tell me a bit about your role and how involved you are with API testing or debugging?”
16
- Current workflow
17
- “How do you currently test APIs day to day?”
18
- “What tools do you use for exploratory API work?”
19
- “What tools do you use for repeatable automated API tests?”
20
- “Are those the same tools, or different?”
21
- “Who usually owns API regression coverage in your team?”
22
- “When there’s a bug, how do developers usually reproduce it?”
23
- Pain discovery
24
- “What’s the most annoying part of your current setup?”
25
- “Where does the process break down?”
26
- “Do developers and QA use the same tools or mostly different ones?”
27
- “Have you ever had useful API tests or requests trapped in one place where other people don’t really use them?”
28
- “What becomes hard to maintain as the project grows?”
29
- “If you could magically remove one frustration from your API workflow, what would it be?”
30
- Team and buying context
31
- “If a new tool genuinely improved this, who would care most?”
32
- “Who would likely use it first?”
33
- “Who would need to approve it?”
34
- “Would this be more of a team decision, an engineering decision, or something individuals would adopt first?”
35
- Reaction to the concept
36
- After you’ve listened first, say this:
37
- “I’m building something that lets developers and QA write readable requests, reusable definitions, environments, assertions, and multi-step sequences directly in VS Code, with the aim of reducing the gap between exploratory work and automation. Based on what you’ve said, what parts of that sound useful and what parts don’t?”
38
- Then ask:
39
- “What would make you want to try something like that?”
40
- “What would make you ignore it?”
41
- “What would you compare it against immediately?”
42
- “What would it need to do well before you’d take it seriously?”
43
- Pilot questions
44
- “If I gave you access to try this on a real workflow, what would be the best use case to test first?”
45
- “What would success look like for you in a pilot?”
46
- “What would need to happen for you to keep using it after the trial?”
47
- “If it worked well, would you see this as something for just you, or for your wider team?”
48
- Close
49
- “This has been really useful. Would you be open to a follow-up once I’ve tightened the product and pilot setup?”
@@ -1,11 +0,0 @@
1
- ---
2
- created: 2026-04-09T23:00:11.428Z
3
- updated: 2026-04-09T23:02:58.760Z
4
- assigned: ""
5
- progress: 0
6
- tags:
7
- - 'Week One'
8
- due: 2026-04-12T00:00:00.000Z
9
- ---
10
-
11
- # Make a list of 10 people to speak to
@@ -1,11 +0,0 @@
1
- ---
2
- created: 2026-04-09T23:00:42.150Z
3
- updated: 2026-04-09T23:02:55.974Z
4
- assigned: ""
5
- progress: 0
6
- tags:
7
- - 'Week One'
8
- due: 2026-04-12T00:00:00.000Z
9
- ---
10
-
11
- # Prepare your customer interview questions
@@ -1,9 +0,0 @@
1
- ---
2
- created: 2026-04-09T23:04:26.660Z
3
- updated: 2026-04-09T23:04:26.657Z
4
- assigned: ""
5
- progress: 0
6
- tags: []
7
- ---
8
-
9
- # Recruit 2–3 pilot users
@@ -1,9 +0,0 @@
1
- ---
2
- created: 2026-04-09T23:04:09.931Z
3
- updated: 2026-04-09T23:04:09.930Z
4
- assigned: ""
5
- progress: 0
6
- tags: []
7
- ---
8
-
9
- # Refine your pitch
@@ -1,9 +0,0 @@
1
- ---
2
- created: 2026-04-30T21:13:26.675Z
3
- updated: 2026-04-30T21:13:26.669Z
4
- assigned: ""
5
- progress: 0
6
- tags: []
7
- ---
8
-
9
- # Use the shiplight website as a template to improve norn website
@@ -1,9 +0,0 @@
1
- ---
2
- created: 2026-04-09T23:03:51.949Z
3
- updated: 2026-04-09T23:03:51.947Z
4
- assigned: ""
5
- progress: 0
6
- tags: []
7
- ---
8
-
9
- # Write down repeated wording
@@ -1,27 +0,0 @@
1
- ---
2
- created: 2026-04-09T22:56:55.687Z
3
- updated: 2026-04-09T23:14:25.954Z
4
- assigned: ""
5
- progress: 1
6
- tags:
7
- - 'Week One'
8
- due: 2026-04-12T00:00:00.000Z
9
- completed: 2026-04-10T00:00:00.000Z
10
- ---
11
-
12
- # Write the one pager
13
-
14
- Before your first mentor call, write one page with only these headings:
15
- What Norn is
16
- Who it is for
17
- What problem it solves
18
- What people use today instead
19
- Why those alternatives are painful
20
- What is already built
21
- What proof you have so far
22
- What you need help deciding next
23
-
24
- ## Comments
25
-
26
- - date: 2026-04-09T23:11:55.306Z
27
- https://docs.google.com/document/d/10L0HINdu6bvcKK5FhMBthPHUQYFKo7ix8OBFo-CcodU/edit?usp=sharing