thumbgate 1.15.0 → 1.16.1

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 (129) hide show
  1. package/.claude-plugin/marketplace.json +6 -6
  2. package/.claude-plugin/plugin.json +3 -3
  3. package/.well-known/llms.txt +5 -5
  4. package/.well-known/mcp/server-card.json +1 -1
  5. package/README.md +59 -35
  6. package/adapters/chatgpt/openapi.yaml +118 -2
  7. package/adapters/claude/.mcp.json +2 -2
  8. package/adapters/mcp/server-stdio.js +210 -84
  9. package/adapters/opencode/opencode.json +1 -1
  10. package/bench/prompt-eval-suite.json +5 -1
  11. package/bin/cli.js +157 -8
  12. package/config/evals/agent-safety-eval.json +338 -22
  13. package/config/gates/routine.json +43 -0
  14. package/config/github-about.json +3 -3
  15. package/config/model-candidates.json +131 -0
  16. package/openapi/openapi.yaml +118 -2
  17. package/package.json +57 -49
  18. package/public/blog.html +7 -7
  19. package/public/codex-plugin.html +6 -6
  20. package/public/compare.html +29 -23
  21. package/public/dashboard.html +82 -10
  22. package/public/guide.html +28 -28
  23. package/public/index.html +216 -98
  24. package/public/learn.html +50 -22
  25. package/public/lessons.html +1 -1
  26. package/public/numbers.html +17 -17
  27. package/public/pro.html +82 -18
  28. package/scripts/agent-audit-trace.js +55 -0
  29. package/scripts/agent-memory-lifecycle.js +96 -0
  30. package/scripts/agent-readiness-plan.js +118 -0
  31. package/scripts/agentic-data-pipeline.js +21 -1
  32. package/scripts/agents-sdk-sandbox-plan.js +57 -0
  33. package/scripts/ai-org-governance.js +98 -0
  34. package/scripts/ai-search-distribution.js +43 -0
  35. package/scripts/artifact-agent-plan.js +81 -0
  36. package/scripts/billing.js +27 -8
  37. package/scripts/cli-schema.js +18 -2
  38. package/scripts/code-mode-mcp-plan.js +71 -0
  39. package/scripts/context-engine.js +1 -2
  40. package/scripts/context-manager.js +4 -1
  41. package/scripts/dashboard-render-spec.js +1 -1
  42. package/scripts/dashboard.js +275 -9
  43. package/scripts/decision-journal.js +13 -3
  44. package/scripts/document-workflow-governance.js +62 -0
  45. package/scripts/enterprise-agent-rollout.js +34 -0
  46. package/scripts/experience-replay-governance.js +69 -0
  47. package/scripts/export-hf-dataset.js +1 -1
  48. package/scripts/feedback-loop.js +92 -4
  49. package/scripts/feedback-to-rules.js +17 -23
  50. package/scripts/gates-engine.js +4 -6
  51. package/scripts/growth-campaigns.js +49 -0
  52. package/scripts/harness-selector.js +16 -4
  53. package/scripts/hybrid-supervisor-agent.js +64 -0
  54. package/scripts/inference-cache-policy.js +72 -0
  55. package/scripts/inference-economics.js +53 -0
  56. package/scripts/internal-agent-bootstrap.js +12 -2
  57. package/scripts/knowledge-layer-plan.js +108 -0
  58. package/scripts/lesson-inference.js +183 -44
  59. package/scripts/lesson-search.js +4 -1
  60. package/scripts/llm-client.js +157 -26
  61. package/scripts/mailer/resend-mailer.js +112 -1
  62. package/scripts/mcp-transport-strategy.js +66 -0
  63. package/scripts/memory-store-governance.js +60 -0
  64. package/scripts/meta-agent-loop.js +7 -13
  65. package/scripts/model-access-eligibility.js +38 -0
  66. package/scripts/model-migration-readiness.js +55 -0
  67. package/scripts/operational-integrity.js +96 -3
  68. package/scripts/otel-declarative-config.js +56 -0
  69. package/scripts/perplexity-client.js +1 -1
  70. package/scripts/post-training-governance.js +34 -0
  71. package/scripts/private-core-boundary.js +72 -0
  72. package/scripts/production-agent-readiness.js +40 -0
  73. package/scripts/prompt-eval.js +564 -32
  74. package/scripts/prompt-programs.js +93 -0
  75. package/scripts/provider-action-normalizer.js +585 -0
  76. package/scripts/scaling-law-claims.js +60 -0
  77. package/scripts/security-scanner.js +1 -1
  78. package/scripts/self-distill-agent.js +7 -32
  79. package/scripts/seo-gsd.js +232 -55
  80. package/scripts/skill-rag-router.js +53 -0
  81. package/scripts/spec-gate.js +1 -1
  82. package/scripts/student-consistent-training.js +73 -0
  83. package/scripts/synthetic-data-provenance.js +98 -0
  84. package/scripts/task-context-result.js +81 -0
  85. package/scripts/telemetry-analytics.js +149 -0
  86. package/scripts/thompson-sampling.js +2 -2
  87. package/scripts/token-savings.js +7 -6
  88. package/scripts/token-tco.js +46 -0
  89. package/scripts/tool-registry.js +63 -3
  90. package/scripts/verification-loop.js +10 -1
  91. package/scripts/verifier-scoring.js +71 -0
  92. package/scripts/workflow-sentinel.js +284 -28
  93. package/scripts/workspace-agent-routines.js +118 -0
  94. package/src/api/server.js +381 -120
  95. package/scripts/analytics-report.js +0 -328
  96. package/scripts/autonomous-workflow.js +0 -377
  97. package/scripts/billing-setup.js +0 -109
  98. package/scripts/creator-campaigns.js +0 -239
  99. package/scripts/cross-encoder-reranker.js +0 -235
  100. package/scripts/daemon-manager.js +0 -108
  101. package/scripts/decision-trace.js +0 -354
  102. package/scripts/delegation-runtime.js +0 -896
  103. package/scripts/dispatch-brief.js +0 -159
  104. package/scripts/distribution-surfaces.js +0 -110
  105. package/scripts/feedback-history-distiller.js +0 -382
  106. package/scripts/funnel-analytics.js +0 -35
  107. package/scripts/history-distiller.js +0 -200
  108. package/scripts/hosted-job-launcher.js +0 -256
  109. package/scripts/intent-router.js +0 -392
  110. package/scripts/lesson-reranker.js +0 -263
  111. package/scripts/lesson-retrieval.js +0 -148
  112. package/scripts/managed-lesson-agent.js +0 -183
  113. package/scripts/operational-dashboard.js +0 -103
  114. package/scripts/operational-summary.js +0 -129
  115. package/scripts/operator-artifacts.js +0 -608
  116. package/scripts/optimize-context.js +0 -17
  117. package/scripts/org-dashboard.js +0 -206
  118. package/scripts/partner-orchestration.js +0 -146
  119. package/scripts/predictive-insights.js +0 -356
  120. package/scripts/pulse.js +0 -80
  121. package/scripts/reflector-agent.js +0 -221
  122. package/scripts/sales-pipeline.js +0 -681
  123. package/scripts/session-episode-store.js +0 -329
  124. package/scripts/session-health-sensor.js +0 -242
  125. package/scripts/session-report.js +0 -120
  126. package/scripts/swarm-coordinator.js +0 -81
  127. package/scripts/tool-kpi-tracker.js +0 -12
  128. package/scripts/webhook-delivery.js +0 -62
  129. package/scripts/workflow-sprint-intake.js +0 -475
@@ -1,328 +0,0 @@
1
- 'use strict';
2
-
3
- const https = require('https');
4
- const { PRODUCTHUNT_URL } = require('./distribution-surfaces');
5
- const { getOperationalBillingSummary } = require('./operational-summary');
6
- const { summarizeCreatorPerformance } = require('./creator-campaigns');
7
- const { getFeedbackPaths } = require('./feedback-loop');
8
- const { buildPredictiveInsights } = require('./predictive-insights');
9
- const { getTelemetryAnalytics } = require('./telemetry-analytics');
10
-
11
- const NPM_PACKAGE = 'thumbgate';
12
- const GITHUB_REPO = 'IgorGanapolsky/ThumbGate';
13
- const PLAUSIBLE_URL = 'https://plausible.io/thumbgate-production.up.railway.app';
14
- const LANDING_PAGE = 'https://thumbgate-production.up.railway.app';
15
-
16
- function httpsGet(url) {
17
- return new Promise((resolve, reject) => {
18
- https.get(url, { headers: { 'User-Agent': 'thumbgate-analytics' } }, (res) => {
19
- let data = '';
20
- res.on('data', (chunk) => { data += chunk; });
21
- res.on('end', () => {
22
- if (res.statusCode >= 400) {
23
- reject(new Error(`HTTP ${res.statusCode} for ${url}: ${data}`));
24
- return;
25
- }
26
- try { resolve(JSON.parse(data)); } catch (e) { reject(e); }
27
- });
28
- }).on('error', reject);
29
- });
30
- }
31
-
32
- function sparkline(values) {
33
- const chars = '▁▂▃▄▅▆▇█';
34
- const max = Math.max(...values, 1);
35
- return values.map((v) => chars[Math.min(Math.floor((v / max) * (chars.length - 1)), chars.length - 1)]).join('');
36
- }
37
-
38
- async function fetchNpmMonthly() {
39
- return httpsGet(`https://api.npmjs.org/downloads/range/last-month/${NPM_PACKAGE}`);
40
- }
41
-
42
- async function fetchNpmWeekly() {
43
- return httpsGet(`https://api.npmjs.org/downloads/point/last-week/${NPM_PACKAGE}`);
44
- }
45
-
46
- async function fetchGitHub() {
47
- return httpsGet(`https://api.github.com/repos/${GITHUB_REPO}`);
48
- }
49
-
50
- async function fetchNpmVersions() {
51
- return httpsGet(`https://registry.npmjs.org/${NPM_PACKAGE}`);
52
- }
53
-
54
- function loadTelemetrySnapshot() {
55
- const { FEEDBACK_DIR } = getFeedbackPaths();
56
- return getTelemetryAnalytics(FEEDBACK_DIR);
57
- }
58
-
59
- function getCounterValue(counter = {}, key) {
60
- return Number(counter && counter[key]) || 0;
61
- }
62
-
63
- function mergeDimensionCounters(metricCounters = {}) {
64
- const metrics = Object.keys(metricCounters);
65
- const rows = new Map();
66
-
67
- for (const metric of metrics) {
68
- const counter = metricCounters[metric] || {};
69
- for (const [rawKey, rawValue] of Object.entries(counter)) {
70
- const key = String(rawKey || '').trim() || 'unknown';
71
- const row = rows.get(key) || { key };
72
- row[metric] = Number(rawValue || 0);
73
- rows.set(key, row);
74
- }
75
- }
76
-
77
- return Array.from(rows.values());
78
- }
79
-
80
- function buildPredictiveStagingModel(telemetry = {}, billingSummary = {}) {
81
- return {
82
- dims: {
83
- sources: mergeDimensionCounters({
84
- pageViews: telemetry.visitors && telemetry.visitors.bySource,
85
- checkoutStarts: telemetry.ctas && telemetry.ctas.checkoutStartsBySource,
86
- acquisitionLeads: billingSummary.attribution && billingSummary.attribution.acquisitionBySource,
87
- paidCustomers: billingSummary.attribution && billingSummary.attribution.paidBySource,
88
- bookedRevenueCents: billingSummary.attribution && billingSummary.attribution.bookedRevenueBySourceCents,
89
- }),
90
- creators: mergeDimensionCounters({
91
- pageViews: telemetry.visitors && telemetry.visitors.byCreator,
92
- checkoutStarts: telemetry.ctas && telemetry.ctas.checkoutStartsByCreator,
93
- acquisitionLeads: billingSummary.attribution && billingSummary.attribution.acquisitionByCreator,
94
- paidCustomers: billingSummary.attribution && billingSummary.attribution.paidByCreator,
95
- bookedRevenueCents: billingSummary.attribution && billingSummary.attribution.bookedRevenueByCreatorCents,
96
- workflowSprintLeads: billingSummary.pipeline && billingSummary.pipeline.workflowSprintLeads && billingSummary.pipeline.workflowSprintLeads.byCreator,
97
- qualifiedWorkflowSprintLeads: billingSummary.pipeline && billingSummary.pipeline.qualifiedWorkflowSprintLeads && billingSummary.pipeline.qualifiedWorkflowSprintLeads.byCreator,
98
- }),
99
- },
100
- };
101
- }
102
-
103
- function resolveProductHuntCount(primaryCounter = {}, secondaryCounter = {}) {
104
- const primary = getCounterValue(primaryCounter, 'producthunt');
105
- if (primary > 0) return primary;
106
- return getCounterValue(secondaryCounter, 'producthunt');
107
- }
108
-
109
- async function collectAnalytics(fetchers = {}) {
110
- const fetchMonthly = fetchers.fetchNpmMonthly || fetchNpmMonthly;
111
- const fetchWeekly = fetchers.fetchNpmWeekly || fetchNpmWeekly;
112
- const fetchRepo = fetchers.fetchGitHub || fetchGitHub;
113
- const fetchVersions = fetchers.fetchNpmVersions || fetchNpmVersions;
114
- const fetchTelemetry = fetchers.fetchTelemetry || loadTelemetrySnapshot;
115
- const fetchBillingSummary = fetchers.fetchBillingSummary || (async () => {
116
- const result = await getOperationalBillingSummary();
117
- return result.summary;
118
- });
119
-
120
- const [monthly, weekly, github, npmMeta, telemetry, billingSummary] = await Promise.all([
121
- fetchMonthly(),
122
- fetchWeekly(),
123
- fetchRepo(),
124
- fetchVersions().catch(() => null),
125
- Promise.resolve().then(() => fetchTelemetry()).catch(() => null),
126
- Promise.resolve().then(() => fetchBillingSummary()).catch(() => null),
127
- ]);
128
-
129
- return { monthly, weekly, github, npmMeta, telemetry, billingSummary };
130
- }
131
-
132
- /**
133
- * Estimate organic downloads by filtering out publish-day inflation.
134
- * npm registry mirrors, bot crawlers (socket.dev, snyk, bundlephobia),
135
- * and npm-stat bots all re-download on publish events.
136
- * Weekend days with no publishes give the organic baseline.
137
- */
138
- function estimateOrganicDownloads(dailyDownloads, publishDates) {
139
- const publishSet = new Set(publishDates);
140
-
141
- let weekendNoPublishTotal = 0;
142
- let weekendNoPublishCount = 0;
143
- let publishDayTotal = 0;
144
- let publishDayCount = 0;
145
- let noPublishDayTotal = 0;
146
- let noPublishDayCount = 0;
147
-
148
- for (const day of dailyDownloads) {
149
- const dt = new Date(day.day + 'T00:00:00Z');
150
- const isWeekend = dt.getUTCDay() === 0 || dt.getUTCDay() === 6;
151
- const isPublishDay = publishSet.has(day.day);
152
-
153
- if (isPublishDay) {
154
- publishDayTotal += day.downloads;
155
- publishDayCount++;
156
- } else {
157
- noPublishDayTotal += day.downloads;
158
- noPublishDayCount++;
159
- if (isWeekend) {
160
- weekendNoPublishTotal += day.downloads;
161
- weekendNoPublishCount++;
162
- }
163
- }
164
- }
165
-
166
- const organicDailyBaseline = weekendNoPublishCount > 0
167
- ? Math.round(weekendNoPublishTotal / weekendNoPublishCount)
168
- : (noPublishDayCount > 0 ? Math.round(noPublishDayTotal / noPublishDayCount) : 0);
169
-
170
- const totalDownloads = dailyDownloads.reduce((s, d) => s + d.downloads, 0);
171
- const estimatedOrganic30d = organicDailyBaseline * 30;
172
- const estimatedInflated = totalDownloads - estimatedOrganic30d;
173
- const organicRate = totalDownloads > 0 ? (estimatedOrganic30d / totalDownloads * 100) : 0;
174
-
175
- return {
176
- organicDailyBaseline,
177
- estimatedOrganic30d,
178
- estimatedInflated: Math.max(0, estimatedInflated),
179
- organicRate: Math.min(100, organicRate),
180
- organicWeekly: organicDailyBaseline * 7,
181
- publishDayAvg: publishDayCount > 0 ? Math.round(publishDayTotal / publishDayCount) : 0,
182
- noPublishDayAvg: noPublishDayCount > 0 ? Math.round(noPublishDayTotal / noPublishDayCount) : 0,
183
- publishDayCount,
184
- totalDownloads,
185
- };
186
- }
187
-
188
- function formatCreatorRows(telemetry = null, billingSummary = null) {
189
- return summarizeCreatorPerformance(telemetry, billingSummary).map((entry, index) => {
190
- const revenueDollars = (entry.bookedRevenueCents / 100).toFixed(2);
191
- return ` ${index + 1}. ${entry.creator} — rev $${revenueDollars}, paid ${entry.paidOrders}, sprint ${entry.qualifiedSprintLeads}/${entry.sprintLeads}, checkouts ${entry.checkoutStarts}, visitors ${entry.visitors}`;
192
- });
193
- }
194
-
195
- function formatReport(monthly, weekly, github, npmMeta, telemetry = null, billingSummary = null) {
196
- const weeklyDownloads = weekly.downloads || 0;
197
- const allDays = monthly.downloads || [];
198
- const monthlyDownloads = allDays.reduce((sum, d) => sum + d.downloads, 0);
199
- const dailyValues = allDays.slice(-7).map((d) => d.downloads);
200
- const trend = sparkline(dailyValues);
201
-
202
- // Extract publish dates from npm registry metadata
203
- const publishDates = [];
204
- if (npmMeta && npmMeta.time) {
205
- for (const [version, timestamp] of Object.entries(npmMeta.time)) {
206
- if (version !== 'created' && version !== 'modified') {
207
- publishDates.push(timestamp.slice(0, 10));
208
- }
209
- }
210
- }
211
-
212
- const organic = estimateOrganicDownloads(allDays, publishDates);
213
- const productHuntVisitors = telemetry
214
- ? resolveProductHuntCount(telemetry.visitors.byTrafficChannel, telemetry.visitors.bySource)
215
- : 0;
216
- const productHuntCtas = telemetry
217
- ? resolveProductHuntCount(telemetry.ctas.byTrafficChannel, telemetry.ctas.bySource)
218
- : 0;
219
- const productHuntCheckouts = telemetry
220
- ? resolveProductHuntCount(telemetry.ctas.checkoutStartsByTrafficChannel, telemetry.ctas.checkoutStartsBySource)
221
- : 0;
222
- const telemetryWindow = telemetry && telemetry.window ? telemetry.window : 'all';
223
- const telemetryLastSeen = telemetry && telemetry.latestSeenAt ? telemetry.latestSeenAt : 'none';
224
- const creatorRows = formatCreatorRows(telemetry, billingSummary);
225
- const predictive = buildPredictiveInsights({
226
- telemetryAnalytics: telemetry || {},
227
- billingSummary: billingSummary || {},
228
- stagingModel: buildPredictiveStagingModel(telemetry || {}, billingSummary || {}),
229
- });
230
-
231
- const lines = [
232
- '',
233
- '╔══════════════════════════════════════════════════════════════════╗',
234
- '║ ThumbGate — Unified Analytics Snapshot ║',
235
- '╚══════════════════════════════════════════════════════════════════╝',
236
- '',
237
- '📦 npm — thumbgate (REPORTED)',
238
- ` Weekly downloads: ${weeklyDownloads.toLocaleString()}`,
239
- ` Monthly downloads: ${monthlyDownloads.toLocaleString()}`,
240
- ` Daily trend (7d): ${trend} [${dailyValues.join(', ')}]`,
241
- '',
242
- '🔬 npm — ORGANIC ESTIMATE (excluding publish-day + bot inflation)',
243
- ` Organic baseline: ~${organic.organicDailyBaseline}/day → ~${organic.organicWeekly}/week → ~${organic.estimatedOrganic30d.toLocaleString()}/month`,
244
- ` Publish-day avg: ${organic.publishDayAvg}/day (${organic.publishDayCount} publish days this period)`,
245
- ` No-publish avg: ${organic.noPublishDayAvg}/day`,
246
- ` Organic rate: ${organic.organicRate.toFixed(1)}% of reported downloads`,
247
- ` Inflated by: ~${organic.estimatedInflated.toLocaleString()} downloads (registry mirrors, bots, self-installs)`,
248
- '',
249
- '⭐ GitHub — IgorGanapolsky/ThumbGate',
250
- ` Stars: ${(github.stargazers_count || 0).toLocaleString()}`,
251
- ` Forks: ${(github.forks_count || 0).toLocaleString()}`,
252
- ` Open issues: ${(github.open_issues_count || 0).toLocaleString()}`,
253
- ` Watchers: ${(github.subscribers_count || 0).toLocaleString()}`,
254
- '',
255
- '🌐 Landing Page',
256
- ` Plausible: ${PLAUSIBLE_URL}`,
257
- ` ⚠️ Check Plausible to exclude your own IP (Settings → filter)`,
258
- '',
259
- '🚀 ProductHunt',
260
- ` Listing: ${PRODUCTHUNT_URL}`,
261
- ` Tracked: utm_source=producthunt → traffic_channel=producthunt`,
262
- ` Visitors: ${productHuntVisitors}`,
263
- ` CTA clicks: ${productHuntCtas}`,
264
- ` Checkouts: ${productHuntCheckouts}`,
265
- ` Window: ${telemetryWindow}`,
266
- ` Last seen: ${telemetryLastSeen}`,
267
- '',
268
- '🎥 Creator Partnerships',
269
- ' Ranked: booked revenue → paid orders → qualified sprint leads → checkouts',
270
- ...(creatorRows.length > 0 ? creatorRows : [' No attributed creator campaigns yet.']),
271
- '',
272
- '🔮 Predictive Insights',
273
- ` Pro propensity: ${predictive.upgradePropensity.pro.band} (${predictive.upgradePropensity.pro.score})`,
274
- ` Team propensity: ${predictive.upgradePropensity.team.band} (${predictive.upgradePropensity.team.score})`,
275
- ` Revenue forecast: $${(predictive.revenueForecast.predictedBookedRevenueCents / 100).toFixed(2)} (+$${(predictive.revenueForecast.incrementalOpportunityCents / 100).toFixed(2)} opportunity)`,
276
- ` Predictive alerts:${predictive.anomalySummary.count} (${predictive.anomalySummary.severity})`,
277
- ...(predictive.topCreators[0]
278
- ? [` Top creator opp: ${predictive.topCreators[0].key} → +$${(predictive.topCreators[0].opportunityRevenueCents / 100).toFixed(2)}`]
279
- : [' Top creator opp: none yet']),
280
- ...(predictive.topSources[0]
281
- ? [` Top channel opp: ${predictive.topSources[0].key} → +$${(predictive.topSources[0].opportunityRevenueCents / 100).toFixed(2)}`]
282
- : [' Top channel opp: none yet']),
283
- '',
284
- '🔗 UTM links for sharing (tracks referral source in Plausible)',
285
- ` Twitter: ${LANDING_PAGE}?utm_source=twitter&utm_medium=social&utm_campaign=launch`,
286
- ` LinkedIn: ${LANDING_PAGE}?utm_source=linkedin&utm_medium=social&utm_campaign=launch`,
287
- ` Reddit: ${LANDING_PAGE}?utm_source=reddit&utm_medium=social&utm_campaign=launch`,
288
- ` HackerNews: ${LANDING_PAGE}?utm_source=hackernews&utm_medium=social&utm_campaign=launch`,
289
- '',
290
- '📏 HONEST METRICS (use these, not the inflated ones)',
291
- ` Real npm traction: ~${organic.organicWeekly} downloads/week`,
292
- ` GitHub stars: ${(github.stargazers_count || 0)}`,
293
- ` ProductHunt: check listing for upvotes/followers`,
294
- ` Landing page: check Plausible (exclude your IP!)`,
295
- '',
296
- ];
297
-
298
- return lines.join('\n');
299
- }
300
-
301
- async function run(options = {}) {
302
- const log = options.log || console.log;
303
- const error = options.error || console.error;
304
- const exit = options.exit || process.exit;
305
-
306
- try {
307
- const { monthly, weekly, github, npmMeta, telemetry, billingSummary } = await collectAnalytics(options.fetchers);
308
- log(formatReport(monthly, weekly, github, npmMeta, telemetry, billingSummary));
309
- } catch (err) {
310
- error('Analytics fetch failed:', err.message);
311
- exit(1);
312
- }
313
- }
314
-
315
- module.exports = {
316
- run,
317
- collectAnalytics,
318
- formatReport,
319
- fetchNpmMonthly,
320
- fetchNpmWeekly,
321
- fetchGitHub,
322
- fetchNpmVersions,
323
- estimateOrganicDownloads,
324
- };
325
-
326
- if (require.main === module) {
327
- run();
328
- }
@@ -1,377 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- const fs = require('node:fs');
5
- const path = require('node:path');
6
-
7
- const { ensureDir } = require('./fs-utils');
8
- const {
9
- executeJob,
10
- readJobState,
11
- resumeJob,
12
- } = require('./async-job-runner');
13
- const {
14
- createCheckpoint,
15
- advanceCheckpoint,
16
- loadCheckpoint,
17
- saveCheckpoint,
18
- } = require('./workflow-gate-checkpoint');
19
- const { appendWorkflowRun } = require('./workflow-runs');
20
-
21
- function normalizeText(value) {
22
- if (value === undefined || value === null) return '';
23
- return String(value).trim();
24
- }
25
-
26
- function slugify(value, fallback = 'workflow') {
27
- // Avoid any `-+` quantifier in an edge-anchored regex (Sonar javascript:S5852
28
- // still flags even the anchored form). Strip edge dashes with a linear scan.
29
- const collapsed = normalizeText(value).toLowerCase().replace(/[^a-z0-9]+/g, '-');
30
- let start = 0;
31
- let end = collapsed.length;
32
- while (start < end && collapsed.charCodeAt(start) === 45) start += 1;
33
- while (end > start && collapsed.charCodeAt(end - 1) === 45) end -= 1;
34
- const normalized = collapsed.slice(start, end);
35
- return normalized || fallback;
36
- }
37
-
38
- function getWorkflowPaths(workflowId, cwd = process.cwd()) {
39
- const rootDir = path.join(cwd, '.thumbgate', 'autonomous-workflows', workflowId);
40
- return {
41
- rootDir,
42
- checkpointPath: path.join(rootDir, 'checkpoint.json'),
43
- reportJsonPath: path.join(rootDir, 'report.json'),
44
- reportMdPath: path.join(rootDir, 'report.md'),
45
- planPath: path.join(rootDir, 'plan.json'),
46
- };
47
- }
48
-
49
- function normalizePlan(input, workflowId) {
50
- if (Array.isArray(input)) {
51
- return {
52
- workflowId,
53
- summary: input.map((step) => normalizeText(step)).filter(Boolean).join(' | ') || 'Execution plan ready',
54
- steps: input
55
- .map((step, index) => ({
56
- id: `step_${index + 1}`,
57
- description: normalizeText(step),
58
- }))
59
- .filter((step) => step.description),
60
- };
61
- }
62
-
63
- if (input && typeof input === 'object') {
64
- const steps = Array.isArray(input.steps)
65
- ? input.steps
66
- .map((step, index) => {
67
- if (typeof step === 'string') {
68
- return {
69
- id: `step_${index + 1}`,
70
- description: normalizeText(step),
71
- };
72
- }
73
-
74
- if (step && typeof step === 'object') {
75
- return {
76
- id: normalizeText(step.id) || `step_${index + 1}`,
77
- description: normalizeText(step.description || step.summary || step.name),
78
- };
79
- }
80
-
81
- return null;
82
- })
83
- .filter(Boolean)
84
- : [];
85
-
86
- return {
87
- workflowId,
88
- summary: normalizeText(input.summary) || steps.map((step) => step.description).join(' | ') || 'Execution plan ready',
89
- steps,
90
- };
91
- }
92
-
93
- const summary = normalizeText(input) || 'Execution plan ready';
94
- return {
95
- workflowId,
96
- summary,
97
- steps: summary ? [{ id: 'step_1', description: summary }] : [],
98
- };
99
- }
100
-
101
- function buildDefaultPlan(spec, workflowId) {
102
- const executionSteps = Array.isArray(spec.stages)
103
- ? spec.stages.map((stage, index) => normalizeText(stage && (stage.name || stage.context || stage.command)) || `Stage ${index + 1}`)
104
- : [];
105
-
106
- return normalizePlan({
107
- summary: normalizeText(spec.planSummary) || `Run ${executionSteps.length || 0} execution stage(s) and verify output`,
108
- steps: [
109
- { id: 'intent', description: normalizeText(spec.intent) || 'Intent captured' },
110
- { id: 'plan', description: 'Execution plan generated' },
111
- ...executionSteps.map((description, index) => ({
112
- id: `execute_${index + 1}`,
113
- description,
114
- })),
115
- { id: 'verify', description: 'Verification loop completed' },
116
- { id: 'report', description: 'Evidence-backed report recorded' },
117
- ],
118
- workflowId,
119
- }, workflowId);
120
- }
121
-
122
- function resolvePlan(spec, workflowId) {
123
- if (typeof spec.plan === 'function') {
124
- return normalizePlan(spec.plan(spec), workflowId);
125
- }
126
-
127
- if (spec.plan) {
128
- return normalizePlan(spec.plan, workflowId);
129
- }
130
-
131
- return buildDefaultPlan(spec, workflowId);
132
- }
133
-
134
- function buildExecutionJob(spec, workflowId, paths, plan) {
135
- return {
136
- id: spec.jobId || `${workflowId}-execution`,
137
- tags: Array.isArray(spec.tags) ? spec.tags : [],
138
- skill: spec.skill || 'autonomous-workflow',
139
- partnerProfile: spec.partnerProfile || null,
140
- verificationMode: spec.verificationMode === 'none' ? 'none' : 'standard',
141
- autoImprove: spec.autoImprove !== false,
142
- recordFeedback: spec.recordFeedback !== false,
143
- stages: Array.isArray(spec.stages) ? spec.stages : [],
144
- metadata: {
145
- workflowId,
146
- planSummary: plan.summary,
147
- workflowRoot: paths.rootDir,
148
- },
149
- };
150
- }
151
-
152
- function writeWorkflowPlan(paths, plan) {
153
- ensureDir(paths.rootDir);
154
- fs.writeFileSync(paths.planPath, `${JSON.stringify(plan, null, 2)}\n`, 'utf8');
155
- return paths.planPath;
156
- }
157
-
158
- function collectEvidenceArtifacts(paths, executionResult, extraArtifacts = []) {
159
- return [
160
- paths.checkpointPath,
161
- paths.planPath,
162
- paths.reportJsonPath,
163
- paths.reportMdPath,
164
- executionResult && executionResult.jobStatePath ? executionResult.jobStatePath : null,
165
- ...extraArtifacts,
166
- ].filter(Boolean);
167
- }
168
-
169
- function writeWorkflowReport(paths, report) {
170
- ensureDir(paths.rootDir);
171
- fs.writeFileSync(paths.reportJsonPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
172
-
173
- const markdown = [
174
- `# ${report.workflowName}`,
175
- '',
176
- `- Workflow ID: ${report.workflowId}`,
177
- `- Status: ${report.status}`,
178
- `- Intent: ${report.intent}`,
179
- `- Verification accepted: ${report.verification ? String(report.verification.accepted) : 'skipped'}`,
180
- `- Evidence artifacts: ${report.evidenceArtifacts.length}`,
181
- '',
182
- '## Plan',
183
- '',
184
- report.plan.summary,
185
- '',
186
- ...report.plan.steps.map((step) => `- ${step.id}: ${step.description}`),
187
- '',
188
- '## Execution',
189
- '',
190
- ...report.execution.stageHistory.map((stage) => `- ${stage.name} @ ${stage.completedAt}`),
191
- '',
192
- '## Evidence Artifacts',
193
- '',
194
- ...report.evidenceArtifacts.map((artifact) => `- ${artifact}`),
195
- ].join('\n');
196
-
197
- fs.writeFileSync(paths.reportMdPath, `${markdown}\n`, 'utf8');
198
- return {
199
- json: paths.reportJsonPath,
200
- markdown: paths.reportMdPath,
201
- };
202
- }
203
-
204
- function recordAutonomousWorkflowRun(spec, report, evidenceArtifacts, feedbackDir) {
205
- const proofBacked = report.status === 'completed'
206
- && (!report.verification || report.verification.accepted)
207
- && evidenceArtifacts.length > 0;
208
-
209
- return appendWorkflowRun({
210
- workflowId: report.workflowId,
211
- workflowName: report.workflowName,
212
- owner: spec.owner || 'automation',
213
- runtime: 'node',
214
- status: report.status,
215
- customerType: spec.customerType || 'internal_dogfood',
216
- teamId: spec.teamId || null,
217
- reviewed: proofBacked,
218
- reviewedBy: proofBacked ? (spec.reviewedBy || 'automation') : null,
219
- proofBacked,
220
- proofArtifacts: evidenceArtifacts,
221
- source: spec.source || 'autonomous-workflow',
222
- metadata: {
223
- intent: report.intent,
224
- planSummary: report.plan.summary,
225
- verificationAttempts: report.verification ? report.verification.attempts : 0,
226
- executionJobId: report.execution.jobId,
227
- },
228
- }, feedbackDir);
229
- }
230
-
231
- function runAutonomousWorkflow(spec = {}, options = {}) {
232
- const cwd = options.cwd || process.cwd();
233
- const workflowId = normalizeText(spec.workflowId) || slugify(spec.name || spec.intent, 'autonomous-workflow');
234
- const workflowName = normalizeText(spec.name) || `Autonomous workflow ${workflowId}`;
235
- const intent = normalizeText(spec.intent) || 'Intent not provided';
236
- const paths = getWorkflowPaths(workflowId, cwd);
237
- const plan = resolvePlan(spec, workflowId);
238
-
239
- writeWorkflowPlan(paths, plan);
240
-
241
- let checkpoint = createCheckpoint({
242
- workflowId,
243
- phase: 'intent',
244
- status: 'running',
245
- intent: { summary: intent },
246
- plan,
247
- evidence: [paths.planPath],
248
- metadata: {
249
- workflowName,
250
- },
251
- });
252
- saveCheckpoint(checkpoint, paths.checkpointPath);
253
-
254
- checkpoint = advanceCheckpoint(checkpoint, {
255
- phase: 'plan',
256
- status: 'running',
257
- plan,
258
- evidence: [paths.planPath],
259
- });
260
- saveCheckpoint(checkpoint, paths.checkpointPath);
261
-
262
- const job = buildExecutionJob(spec, workflowId, paths, plan);
263
- const executionResult = options.resume === true
264
- ? resumeJob(job.id, job)
265
- : executeJob(job);
266
- const jobState = readJobState(job.id);
267
-
268
- checkpoint = advanceCheckpoint(checkpoint, {
269
- phase: 'verify',
270
- status: executionResult.status,
271
- evidence: jobState && jobState.verification ? [paths.checkpointPath] : [],
272
- metadata: {
273
- executionJobId: job.id,
274
- executionStatus: executionResult.status,
275
- },
276
- });
277
- saveCheckpoint(checkpoint, paths.checkpointPath);
278
-
279
- const report = {
280
- workflowId,
281
- workflowName,
282
- status: executionResult.status,
283
- intent,
284
- plan,
285
- execution: {
286
- jobId: job.id,
287
- status: executionResult.status,
288
- stageHistory: Array.isArray(jobState && jobState.stageHistory) ? jobState.stageHistory : [],
289
- checkpointCount: Array.isArray(jobState && jobState.checkpoints) ? jobState.checkpoints.length : 0,
290
- currentContext: jobState && jobState.currentContext ? jobState.currentContext : '',
291
- jobStatePath: jobState ? path.join(getFeedbackDir(options.feedbackDir), 'jobs', job.id, 'state.json') : null,
292
- },
293
- verification: executionResult.phases ? executionResult.phases.verification : null,
294
- phases: executionResult.phases || null,
295
- timestamp: new Date().toISOString(),
296
- evidenceArtifacts: [],
297
- };
298
-
299
- const evidenceArtifacts = collectEvidenceArtifacts(paths, report.execution, spec.proofArtifacts);
300
- report.evidenceArtifacts = evidenceArtifacts;
301
-
302
- checkpoint = advanceCheckpoint(checkpoint, {
303
- phase: 'report',
304
- status: executionResult.status,
305
- report: {
306
- status: report.status,
307
- generatedAt: report.timestamp,
308
- },
309
- evidence: evidenceArtifacts,
310
- });
311
- saveCheckpoint(checkpoint, paths.checkpointPath);
312
-
313
- writeWorkflowReport(paths, report);
314
- report.workflowRun = recordAutonomousWorkflowRun(spec, report, evidenceArtifacts, options.feedbackDir);
315
- fs.writeFileSync(paths.reportJsonPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
316
-
317
- return report;
318
- }
319
-
320
- function getFeedbackDir(feedbackDir) {
321
- if (feedbackDir) return feedbackDir;
322
- return process.env.THUMBGATE_FEEDBACK_DIR || path.join(process.cwd(), '.thumbgate');
323
- }
324
-
325
- function resumeAutonomousWorkflow(spec = {}, options = {}) {
326
- return runAutonomousWorkflow(spec, { ...options, resume: true });
327
- }
328
-
329
- function readWorkflowReport(workflowId, options = {}) {
330
- const paths = getWorkflowPaths(workflowId, options.cwd || process.cwd());
331
- if (!fs.existsSync(paths.reportJsonPath)) return null;
332
- return JSON.parse(fs.readFileSync(paths.reportJsonPath, 'utf8'));
333
- }
334
-
335
- function isCliInvocation(argv = process.argv) {
336
- const invokedPath = argv[1];
337
- return invokedPath ? path.resolve(invokedPath) === __filename : false;
338
- }
339
-
340
- function parseArgs(argv = process.argv.slice(2)) {
341
- const args = {};
342
- for (const arg of argv) {
343
- if (!arg.startsWith('--')) continue;
344
- const [key, ...rest] = arg.slice(2).split('=');
345
- args[key] = rest.length > 0 ? rest.join('=') : true;
346
- }
347
- return args;
348
- }
349
-
350
- if (isCliInvocation()) {
351
- const args = parseArgs();
352
- if (!args.file) {
353
- console.error('Usage: node scripts/autonomous-workflow.js --file=workflow.json [--resume]');
354
- process.exit(1);
355
- }
356
-
357
- const specPath = path.resolve(args.file);
358
- const spec = JSON.parse(fs.readFileSync(specPath, 'utf8'));
359
- const report = args.resume ? resumeAutonomousWorkflow(spec) : runAutonomousWorkflow(spec);
360
- console.log(JSON.stringify(report, null, 2));
361
- process.exit(report.status === 'completed' ? 0 : 1);
362
- }
363
-
364
- module.exports = {
365
- buildDefaultPlan,
366
- collectEvidenceArtifacts,
367
- getWorkflowPaths,
368
- normalizePlan,
369
- parseArgs,
370
- readWorkflowReport,
371
- recordAutonomousWorkflowRun,
372
- resumeAutonomousWorkflow,
373
- runAutonomousWorkflow,
374
- slugify,
375
- writeWorkflowPlan,
376
- writeWorkflowReport,
377
- };