@contractspec/example.product-intent 3.7.6 → 3.7.7

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.
@@ -1,230 +1,230 @@
1
+ import { JiraProjectManagementProvider } from '@contractspec/integration.providers-impls/impls/jira';
2
+ import { LinearProjectManagementProvider } from '@contractspec/integration.providers-impls/impls/linear';
3
+ import { NotionProjectManagementProvider } from '@contractspec/integration.providers-impls/impls/notion';
1
4
  import { createAgentJsonRunner } from '@contractspec/lib.ai-agent/agent/json-runner';
5
+ import type { ProjectManagementProvider } from '@contractspec/lib.contracts-integrations';
6
+ import type { TicketPipelineModelRunner } from '@contractspec/lib.product-intent-utils';
2
7
  import {
3
- buildProjectManagementSyncPayload,
4
- extractEvidence,
5
- generateTickets,
6
- groupProblems,
7
- impactEngine,
8
- suggestPatch,
9
- type TicketPipelineLogger,
8
+ buildProjectManagementSyncPayload,
9
+ extractEvidence,
10
+ generateTickets,
11
+ groupProblems,
12
+ impactEngine,
13
+ suggestPatch,
14
+ type TicketPipelineLogger,
10
15
  } from '@contractspec/lib.product-intent-utils';
11
- import type { TicketPipelineModelRunner } from '@contractspec/lib.product-intent-utils';
12
16
  import { loadEvidenceChunksWithSignals } from './load-evidence';
13
17
  import { resolvePosthogEvidenceOptionsFromEnv } from './posthog-signals';
14
- import { LinearProjectManagementProvider } from '@contractspec/integration.providers-impls/impls/linear';
15
- import { JiraProjectManagementProvider } from '@contractspec/integration.providers-impls/impls/jira';
16
- import { NotionProjectManagementProvider } from '@contractspec/integration.providers-impls/impls/notion';
17
- import type { ProjectManagementProvider } from '@contractspec/lib.contracts-integrations';
18
18
 
19
19
  const QUESTION =
20
- 'Which activation and onboarding friction should we prioritize next?';
20
+ 'Which activation and onboarding friction should we prioritize next?';
21
21
 
22
22
  type ProviderName = 'linear' | 'jira' | 'notion';
23
23
  type AiProviderName = 'openai' | 'anthropic' | 'mistral' | 'gemini' | 'ollama';
24
24
 
25
25
  function resolveProvider(): ProviderName {
26
- const raw = (process.env.CONTRACTSPEC_PM_PROVIDER ?? '').toLowerCase();
27
- if (raw === 'linear' || raw === 'jira' || raw === 'notion') return raw;
28
- throw new Error(
29
- 'Set CONTRACTSPEC_PM_PROVIDER to one of: linear, jira, notion'
30
- );
26
+ const raw = (process.env.CONTRACTSPEC_PM_PROVIDER ?? '').toLowerCase();
27
+ if (raw === 'linear' || raw === 'jira' || raw === 'notion') return raw;
28
+ throw new Error(
29
+ 'Set CONTRACTSPEC_PM_PROVIDER to one of: linear, jira, notion'
30
+ );
31
31
  }
32
32
 
33
33
  async function resolveModelRunner(): Promise<TicketPipelineModelRunner> {
34
- const provider = resolveAiProviderName();
35
- const apiKey = resolveApiKey(provider);
36
- const proxyUrl = process.env.CONTRACTSPEC_AI_PROXY_URL;
37
- const organizationId = process.env.CONTRACTSPEC_ORG_ID;
38
- const baseUrl = process.env.OLLAMA_BASE_URL;
39
- const model =
40
- process.env.CONTRACTSPEC_AI_MODEL ?? process.env.AI_MODEL ?? undefined;
41
-
42
- if (provider !== 'ollama' && !apiKey && !proxyUrl && !organizationId) {
43
- throw new Error(
44
- `Missing API credentials for ${provider}. Set provider API key or CONTRACTSPEC_AI_PROXY_URL.`
45
- );
46
- }
47
-
48
- return createAgentJsonRunner({
49
- provider: {
50
- provider,
51
- model,
52
- apiKey,
53
- baseUrl,
54
- proxyUrl,
55
- organizationId,
56
- },
57
- temperature: 0,
58
- system:
59
- 'You are a product discovery analyst. Respond with strict JSON only and use exact quotes for citations.',
60
- });
34
+ const provider = resolveAiProviderName();
35
+ const apiKey = resolveApiKey(provider);
36
+ const proxyUrl = process.env.CONTRACTSPEC_AI_PROXY_URL;
37
+ const organizationId = process.env.CONTRACTSPEC_ORG_ID;
38
+ const baseUrl = process.env.OLLAMA_BASE_URL;
39
+ const model =
40
+ process.env.CONTRACTSPEC_AI_MODEL ?? process.env.AI_MODEL ?? undefined;
41
+
42
+ if (provider !== 'ollama' && !apiKey && !proxyUrl && !organizationId) {
43
+ throw new Error(
44
+ `Missing API credentials for ${provider}. Set provider API key or CONTRACTSPEC_AI_PROXY_URL.`
45
+ );
46
+ }
47
+
48
+ return createAgentJsonRunner({
49
+ provider: {
50
+ provider,
51
+ model,
52
+ apiKey,
53
+ baseUrl,
54
+ proxyUrl,
55
+ organizationId,
56
+ },
57
+ temperature: 0,
58
+ system:
59
+ 'You are a product discovery analyst. Respond with strict JSON only and use exact quotes for citations.',
60
+ });
61
61
  }
62
62
 
63
63
  function resolveAiProviderName(): AiProviderName {
64
- const raw =
65
- process.env.CONTRACTSPEC_AI_PROVIDER ?? process.env.AI_PROVIDER ?? 'openai';
66
- const normalized = raw.toLowerCase();
67
- const allowed: AiProviderName[] = [
68
- 'openai',
69
- 'anthropic',
70
- 'mistral',
71
- 'gemini',
72
- 'ollama',
73
- ];
74
- if (!allowed.includes(normalized as AiProviderName)) {
75
- throw new Error(
76
- `Unsupported AI provider: ${raw}. Use one of: ${allowed.join(', ')}`
77
- );
78
- }
79
- return normalized as AiProviderName;
64
+ const raw =
65
+ process.env.CONTRACTSPEC_AI_PROVIDER ?? process.env.AI_PROVIDER ?? 'openai';
66
+ const normalized = raw.toLowerCase();
67
+ const allowed: AiProviderName[] = [
68
+ 'openai',
69
+ 'anthropic',
70
+ 'mistral',
71
+ 'gemini',
72
+ 'ollama',
73
+ ];
74
+ if (!allowed.includes(normalized as AiProviderName)) {
75
+ throw new Error(
76
+ `Unsupported AI provider: ${raw}. Use one of: ${allowed.join(', ')}`
77
+ );
78
+ }
79
+ return normalized as AiProviderName;
80
80
  }
81
81
 
82
82
  function resolveApiKey(provider: AiProviderName): string | undefined {
83
- switch (provider.toLowerCase()) {
84
- case 'openai':
85
- return process.env.OPENAI_API_KEY;
86
- case 'anthropic':
87
- return process.env.ANTHROPIC_API_KEY;
88
- case 'mistral':
89
- return process.env.MISTRAL_API_KEY;
90
- case 'gemini':
91
- return process.env.GOOGLE_API_KEY ?? process.env.GEMINI_API_KEY;
92
- case 'ollama':
93
- return undefined;
94
- default:
95
- return undefined;
96
- }
83
+ switch (provider.toLowerCase()) {
84
+ case 'openai':
85
+ return process.env.OPENAI_API_KEY;
86
+ case 'anthropic':
87
+ return process.env.ANTHROPIC_API_KEY;
88
+ case 'mistral':
89
+ return process.env.MISTRAL_API_KEY;
90
+ case 'gemini':
91
+ return process.env.GOOGLE_API_KEY ?? process.env.GEMINI_API_KEY;
92
+ case 'ollama':
93
+ return undefined;
94
+ default:
95
+ return undefined;
96
+ }
97
97
  }
98
98
 
99
99
  function createLogger(): TicketPipelineLogger {
100
- return {
101
- log: () => undefined,
102
- };
100
+ return {
101
+ log: () => undefined,
102
+ };
103
103
  }
104
104
 
105
105
  async function run() {
106
- const provider = resolveProvider();
107
- const modelRunner = await resolveModelRunner();
108
-
109
- const evidenceChunks = await loadEvidenceChunksWithSignals({
110
- posthog: resolvePosthogEvidenceOptionsFromEnv() ?? undefined,
111
- });
112
- const findings = await extractEvidence(evidenceChunks, QUESTION, {
113
- modelRunner,
114
- logger: createLogger(),
115
- maxAttempts: 2,
116
- });
117
- const problems = await groupProblems(findings, QUESTION, {
118
- modelRunner,
119
- logger: createLogger(),
120
- maxAttempts: 2,
121
- });
122
- const tickets = await generateTickets(problems, findings, QUESTION, {
123
- modelRunner,
124
- logger: createLogger(),
125
- maxAttempts: 2,
126
- });
127
- const patchIntent = tickets[0]
128
- ? await suggestPatch(tickets[0], {
129
- modelRunner,
130
- logger: createLogger(),
131
- maxAttempts: 2,
132
- })
133
- : undefined;
134
- const impact = patchIntent ? impactEngine(patchIntent) : undefined;
135
-
136
- const payload = buildProjectManagementSyncPayload({
137
- question: QUESTION,
138
- tickets,
139
- patchIntent,
140
- impact,
141
- options: {
142
- includeSummary: true,
143
- baseTags: ['product-intent'],
144
- defaultPriority: 'medium',
145
- },
146
- });
147
-
148
- if (process.env.CONTRACTSPEC_PM_DRY_RUN === 'true') {
149
- console.log(JSON.stringify(payload, null, 2));
150
- return;
151
- }
152
-
153
- const created = await syncToProvider(provider, payload);
154
- console.log(JSON.stringify(created, null, 2));
106
+ const provider = resolveProvider();
107
+ const modelRunner = await resolveModelRunner();
108
+
109
+ const evidenceChunks = await loadEvidenceChunksWithSignals({
110
+ posthog: resolvePosthogEvidenceOptionsFromEnv() ?? undefined,
111
+ });
112
+ const findings = await extractEvidence(evidenceChunks, QUESTION, {
113
+ modelRunner,
114
+ logger: createLogger(),
115
+ maxAttempts: 2,
116
+ });
117
+ const problems = await groupProblems(findings, QUESTION, {
118
+ modelRunner,
119
+ logger: createLogger(),
120
+ maxAttempts: 2,
121
+ });
122
+ const tickets = await generateTickets(problems, findings, QUESTION, {
123
+ modelRunner,
124
+ logger: createLogger(),
125
+ maxAttempts: 2,
126
+ });
127
+ const patchIntent = tickets[0]
128
+ ? await suggestPatch(tickets[0], {
129
+ modelRunner,
130
+ logger: createLogger(),
131
+ maxAttempts: 2,
132
+ })
133
+ : undefined;
134
+ const impact = patchIntent ? impactEngine(patchIntent) : undefined;
135
+
136
+ const payload = buildProjectManagementSyncPayload({
137
+ question: QUESTION,
138
+ tickets,
139
+ patchIntent,
140
+ impact,
141
+ options: {
142
+ includeSummary: true,
143
+ baseTags: ['product-intent'],
144
+ defaultPriority: 'medium',
145
+ },
146
+ });
147
+
148
+ if (process.env.CONTRACTSPEC_PM_DRY_RUN === 'true') {
149
+ console.log(JSON.stringify(payload, null, 2));
150
+ return;
151
+ }
152
+
153
+ const created = await syncToProvider(provider, payload);
154
+ console.log(JSON.stringify(created, null, 2));
155
155
  }
156
156
 
157
157
  async function syncToProvider(
158
- provider: ProviderName,
159
- payload: ReturnType<typeof buildProjectManagementSyncPayload>
158
+ provider: ProviderName,
159
+ payload: ReturnType<typeof buildProjectManagementSyncPayload>
160
160
  ) {
161
- if (provider === 'linear') {
162
- const client = new LinearProjectManagementProvider({
163
- apiKey: requireEnv('LINEAR_API_KEY'),
164
- teamId: requireEnv('LINEAR_TEAM_ID'),
165
- projectId: process.env.LINEAR_PROJECT_ID,
166
- stateId: process.env.LINEAR_STATE_ID,
167
- assigneeId: process.env.LINEAR_ASSIGNEE_ID,
168
- labelIds: splitList(process.env.LINEAR_LABEL_IDS),
169
- });
170
- return executeSync(client, payload);
171
- }
172
-
173
- if (provider === 'jira') {
174
- const client = new JiraProjectManagementProvider({
175
- siteUrl: requireEnv('JIRA_SITE_URL'),
176
- email: requireEnv('JIRA_EMAIL'),
177
- apiToken: requireEnv('JIRA_API_TOKEN'),
178
- projectKey: process.env.JIRA_PROJECT_KEY,
179
- issueType: process.env.JIRA_ISSUE_TYPE,
180
- defaultLabels: splitList(process.env.JIRA_DEFAULT_LABELS),
181
- });
182
- return executeSync(client, payload);
183
- }
184
-
185
- const client = new NotionProjectManagementProvider({
186
- apiKey: requireEnv('NOTION_API_KEY'),
187
- databaseId: process.env.NOTION_DATABASE_ID,
188
- summaryParentPageId: process.env.NOTION_SUMMARY_PARENT_PAGE_ID,
189
- titleProperty: process.env.NOTION_TITLE_PROPERTY,
190
- statusProperty: process.env.NOTION_STATUS_PROPERTY,
191
- priorityProperty: process.env.NOTION_PRIORITY_PROPERTY,
192
- tagsProperty: process.env.NOTION_TAGS_PROPERTY,
193
- dueDateProperty: process.env.NOTION_DUE_DATE_PROPERTY,
194
- descriptionProperty: process.env.NOTION_DESCRIPTION_PROPERTY,
195
- });
196
- return executeSync(client, payload);
161
+ if (provider === 'linear') {
162
+ const client = new LinearProjectManagementProvider({
163
+ apiKey: requireEnv('LINEAR_API_KEY'),
164
+ teamId: requireEnv('LINEAR_TEAM_ID'),
165
+ projectId: process.env.LINEAR_PROJECT_ID,
166
+ stateId: process.env.LINEAR_STATE_ID,
167
+ assigneeId: process.env.LINEAR_ASSIGNEE_ID,
168
+ labelIds: splitList(process.env.LINEAR_LABEL_IDS),
169
+ });
170
+ return executeSync(client, payload);
171
+ }
172
+
173
+ if (provider === 'jira') {
174
+ const client = new JiraProjectManagementProvider({
175
+ siteUrl: requireEnv('JIRA_SITE_URL'),
176
+ email: requireEnv('JIRA_EMAIL'),
177
+ apiToken: requireEnv('JIRA_API_TOKEN'),
178
+ projectKey: process.env.JIRA_PROJECT_KEY,
179
+ issueType: process.env.JIRA_ISSUE_TYPE,
180
+ defaultLabels: splitList(process.env.JIRA_DEFAULT_LABELS),
181
+ });
182
+ return executeSync(client, payload);
183
+ }
184
+
185
+ const client = new NotionProjectManagementProvider({
186
+ apiKey: requireEnv('NOTION_API_KEY'),
187
+ databaseId: process.env.NOTION_DATABASE_ID,
188
+ summaryParentPageId: process.env.NOTION_SUMMARY_PARENT_PAGE_ID,
189
+ titleProperty: process.env.NOTION_TITLE_PROPERTY,
190
+ statusProperty: process.env.NOTION_STATUS_PROPERTY,
191
+ priorityProperty: process.env.NOTION_PRIORITY_PROPERTY,
192
+ tagsProperty: process.env.NOTION_TAGS_PROPERTY,
193
+ dueDateProperty: process.env.NOTION_DUE_DATE_PROPERTY,
194
+ descriptionProperty: process.env.NOTION_DESCRIPTION_PROPERTY,
195
+ });
196
+ return executeSync(client, payload);
197
197
  }
198
198
 
199
199
  async function executeSync(
200
- client: ProjectManagementProvider,
201
- payload: ReturnType<typeof buildProjectManagementSyncPayload>
200
+ client: ProjectManagementProvider,
201
+ payload: ReturnType<typeof buildProjectManagementSyncPayload>
202
202
  ) {
203
- const summary = payload.summary
204
- ? await client.createWorkItem(payload.summary)
205
- : undefined;
206
- const items = await client.createWorkItems(payload.items);
207
- return { summary, items };
203
+ const summary = payload.summary
204
+ ? await client.createWorkItem(payload.summary)
205
+ : undefined;
206
+ const items = await client.createWorkItems(payload.items);
207
+ return { summary, items };
208
208
  }
209
209
 
210
210
  function requireEnv(key: string): string {
211
- const value = process.env[key];
212
- if (!value) {
213
- throw new Error(`Missing required env var: ${key}`);
214
- }
215
- return value;
211
+ const value = process.env[key];
212
+ if (!value) {
213
+ throw new Error(`Missing required env var: ${key}`);
214
+ }
215
+ return value;
216
216
  }
217
217
 
218
218
  function splitList(value?: string): string[] | undefined {
219
- if (!value) return undefined;
220
- const items = value
221
- .split(',')
222
- .map((item) => item.trim())
223
- .filter(Boolean);
224
- return items.length > 0 ? items : undefined;
219
+ if (!value) return undefined;
220
+ const items = value
221
+ .split(',')
222
+ .map((item) => item.trim())
223
+ .filter(Boolean);
224
+ return items.length > 0 ? items : undefined;
225
225
  }
226
226
 
227
227
  run().catch((error) => {
228
- console.error(error);
229
- process.exitCode = 1;
228
+ console.error(error);
229
+ process.exitCode = 1;
230
230
  });
package/tsconfig.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
- "extends": "@contractspec/tool.typescript/react-library.json",
3
- "compilerOptions": {
4
- "outDir": "./dist",
5
- "rootDir": "./src"
6
- },
7
- "include": ["src/**/*"],
8
- "exclude": ["node_modules", "dist"]
2
+ "extends": "@contractspec/tool.typescript/react-library.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src/**/*"],
8
+ "exclude": ["node_modules", "dist"]
9
9
  }
package/tsdown.config.js CHANGED
@@ -1,16 +1,10 @@
1
- import { defineConfig, moduleLibrary, withDevExports } from '@contractspec/tool.bun';
1
+ import {
2
+ defineConfig,
3
+ moduleLibrary,
4
+ withDevExports,
5
+ } from '@contractspec/tool.bun';
2
6
 
3
7
  export default defineConfig(() => ({
4
- ...moduleLibrary,
5
- ...withDevExports,
8
+ ...moduleLibrary,
9
+ ...withDevExports,
6
10
  }));
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-