@contractspec/example.product-intent 3.7.6 → 3.7.10
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/.turbo/turbo-build.log +12 -10
- package/AGENTS.md +44 -20
- package/CHANGELOG.md +26 -0
- package/README.md +63 -44
- package/dist/contracts.test.d.ts +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +75 -1
- package/dist/load-evidence.js +1 -1
- package/dist/node/index.js +75 -1
- package/dist/node/load-evidence.js +1 -1
- package/dist/node/posthog-signals.js +1 -1
- package/dist/node/product-intent.discovery.js +75 -0
- package/dist/node/script.js +1 -1
- package/dist/node/sync-actions.js +4 -4
- package/dist/posthog-signals.d.ts +1 -1
- package/dist/posthog-signals.js +1 -1
- package/dist/product-intent.discovery.d.ts +4 -0
- package/dist/product-intent.discovery.js +76 -0
- package/dist/script.js +1 -1
- package/dist/sync-actions.js +4 -4
- package/package.json +23 -10
- package/src/contracts.test.ts +14 -0
- package/src/docs/product-intent.docblock.ts +21 -21
- package/src/example.ts +26 -26
- package/src/index.ts +13 -12
- package/src/load-evidence.test.ts +6 -6
- package/src/load-evidence.ts +49 -49
- package/src/posthog-signals.ts +253 -253
- package/src/product-intent.discovery.ts +79 -0
- package/src/product-intent.feature.ts +13 -13
- package/src/script.ts +191 -191
- package/src/sync-actions.ts +185 -185
- package/tsconfig.json +7 -7
- package/tsdown.config.js +7 -13
package/src/sync-actions.ts
CHANGED
|
@@ -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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
return {
|
|
101
|
+
log: () => undefined,
|
|
102
|
+
};
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
async function run() {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
159
|
-
|
|
158
|
+
provider: ProviderName,
|
|
159
|
+
payload: ReturnType<typeof buildProjectManagementSyncPayload>
|
|
160
160
|
) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
201
|
-
|
|
200
|
+
client: ProjectManagementProvider,
|
|
201
|
+
payload: ReturnType<typeof buildProjectManagementSyncPayload>
|
|
202
202
|
) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
229
|
-
|
|
228
|
+
console.error(error);
|
|
229
|
+
process.exitCode = 1;
|
|
230
230
|
});
|
package/tsconfig.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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 {
|
|
1
|
+
import {
|
|
2
|
+
defineConfig,
|
|
3
|
+
moduleLibrary,
|
|
4
|
+
withDevExports,
|
|
5
|
+
} from '@contractspec/tool.bun';
|
|
2
6
|
|
|
3
7
|
export default defineConfig(() => ({
|
|
4
|
-
|
|
5
|
-
|
|
8
|
+
...moduleLibrary,
|
|
9
|
+
...withDevExports,
|
|
6
10
|
}));
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|