thumbgate 1.7.0 → 1.8.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thumbgate-marketplace",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "owner": {
5
5
  "name": "Igor Ganapolsky",
6
6
  "email": "ig5973700@gmail.com"
@@ -13,7 +13,7 @@
13
13
  "source": "npm",
14
14
  "package": "thumbgate"
15
15
  },
16
- "version": "1.7.0",
16
+ "version": "1.8.0",
17
17
  "author": {
18
18
  "name": "Igor Ganapolsky"
19
19
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "thumbgate",
3
3
  "description": "Type 👍 or 👎 on any agent action. ThumbGate captures it, distills a lesson, and blocks the pattern from repeating. One thumbs-down = the agent physically cannot make that mistake again. 33 pre-action gates, budget enforcement, self-protection, and NIST/SOC2 compliance tags.",
4
- "version": "1.7.0",
4
+ "version": "1.8.0",
5
5
  "author": {
6
6
  "name": "Igor Ganapolsky"
7
7
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thumbgate",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "ThumbGate — 👍👎 feedback that teaches your AI agent. Thumbs down a mistake, it never happens again.",
5
5
  "homepage": "https://github.com/IgorGanapolsky/thumbgate",
6
6
  "transport": "stdio",
@@ -3,7 +3,7 @@
3
3
  - `chatgpt/openapi.yaml`: import into GPT Actions.
4
4
  - `gemini/function-declarations.json`: Gemini function-calling definitions.
5
5
  - `mcp/server-stdio.js`: underlying local MCP stdio server implementation.
6
- - `claude/.mcp.json`: example Claude Code MCP config using `npx --yes --package thumbgate@1.7.0 thumbgate serve`.
6
+ - `claude/.mcp.json`: example Claude Code MCP config using `npx --yes --package thumbgate@1.8.0 thumbgate serve`.
7
7
  - `codex/config.toml`: example Codex MCP profile section using the same version-pinned portable launcher.
8
8
  - `amp/skills/thumbgate-feedback/SKILL.md`: Amp skill template.
9
9
  - `opencode/opencode.json`: portable OpenCode MCP profile using the same version-pinned portable launcher.
@@ -2,13 +2,13 @@
2
2
  "mcpServers": {
3
3
  "thumbgate": {
4
4
  "command": "npx",
5
- "args": ["--yes", "--package", "thumbgate@1.7.0", "thumbgate", "serve"]
5
+ "args": ["--yes", "--package", "thumbgate@1.8.0", "thumbgate", "serve"]
6
6
  }
7
7
  },
8
8
  "hooks": {
9
9
  "preToolUse": {
10
10
  "command": "npx",
11
- "args": ["--yes", "--package", "thumbgate@1.7.0", "thumbgate", "gate-check"]
11
+ "args": ["--yes", "--package", "thumbgate@1.8.0", "thumbgate", "gate-check"]
12
12
  }
13
13
  }
14
14
  }
@@ -125,6 +125,8 @@ const {
125
125
  formatUnifiedContext,
126
126
  } = require('../../scripts/context-manager');
127
127
  const { exportHfDataset } = require('../../scripts/export-hf-dataset');
128
+ const { distributeContextToAgents } = require('../../scripts/swarm-coordinator');
129
+ const { buildSessionReport } = require('../../scripts/session-report');
128
130
 
129
131
  const PRO_CHECKOUT_URL = 'https://thumbgate-production.up.railway.app/checkout/pro';
130
132
 
@@ -146,7 +148,7 @@ const {
146
148
  finalizeSession: finalizeFeedbackSession,
147
149
  } = require('../../scripts/feedback-session');
148
150
 
149
- const SERVER_INFO = { name: 'thumbgate-mcp', version: '1.7.0' };
151
+ const SERVER_INFO = { name: 'thumbgate-mcp', version: '1.8.0' };
150
152
  const COMMERCE_CATEGORIES = [
151
153
  'product_recommendation',
152
154
  'brand_compliance',
@@ -736,6 +738,53 @@ async function callToolInner(name, args) {
736
738
  }
737
739
  case 'verify_claim':
738
740
  return toTextResult(verifyClaimEvidence(args.claim));
741
+ case 'require_evidence_for_claim': {
742
+ if (!args.claim || typeof args.claim !== 'string') {
743
+ throw new Error('claim is required and must be a string');
744
+ }
745
+ const verification = verifyClaimEvidence(args.claim);
746
+ const mode = args.mode === 'advisory' ? 'advisory' : 'blocking';
747
+ const hasMatchingChecks = Array.isArray(verification.checks) && verification.checks.length > 0;
748
+ const evidenceMissing = hasMatchingChecks && !verification.verified;
749
+ const blocking = mode === 'blocking' && evidenceMissing;
750
+ const missingActions = hasMatchingChecks
751
+ ? Array.from(new Set(verification.checks.flatMap((check) => check.missing || [])))
752
+ : [];
753
+ try {
754
+ const { recordAuditEvent } = require('../../scripts/audit-trail');
755
+ recordAuditEvent({
756
+ toolName: 'require_evidence_for_claim',
757
+ toolInput: { claim: args.claim, mode, sessionId: args.sessionId || null },
758
+ decision: blocking ? 'deny' : 'allow',
759
+ gateId: 'completion_claim',
760
+ message: blocking
761
+ ? `Completion claim blocked — missing evidence: ${missingActions.join(', ') || 'unknown'}`
762
+ : `Completion claim verified (${verification.verified ? 'evidence present' : 'no matching gate'})`,
763
+ source: 'completion-gate',
764
+ });
765
+ } catch { /* audit write must never break tool response */ }
766
+ return toTextResult({
767
+ claim: args.claim,
768
+ mode,
769
+ blocking,
770
+ verified: verification.verified,
771
+ matchedChecks: hasMatchingChecks,
772
+ missingActions,
773
+ checks: verification.checks,
774
+ sessionId: args.sessionId || null,
775
+ });
776
+ }
777
+ case 'distribute_context_to_agents':
778
+ return toTextResult(distributeContextToAgents({
779
+ query: args.query || '',
780
+ agents: args.agents,
781
+ maxItems: args.maxItems,
782
+ maxChars: args.maxChars,
783
+ namespaces: Array.isArray(args.namespaces) ? args.namespaces : [],
784
+ ttlMs: args.ttlMs,
785
+ }));
786
+ case 'session_report':
787
+ return toTextResult(buildSessionReport({ windowHours: args.windowHours }));
739
788
  case 'check_operational_integrity':
740
789
  return toTextResult(evaluateOperationalIntegrity({
741
790
  repoPath: args.repoPath,
@@ -7,7 +7,7 @@
7
7
  "npx",
8
8
  "--yes",
9
9
  "--package",
10
- "thumbgate@1.7.0",
10
+ "thumbgate@1.8.0",
11
11
  "thumbgate",
12
12
  "serve"
13
13
  ],
@@ -52,6 +52,9 @@
52
52
  "get_reliability_rules",
53
53
  "describe_reliability_entity",
54
54
  "report_product_issue",
55
+ "require_evidence_for_claim",
56
+ "distribute_context_to_agents",
57
+ "session_report",
55
58
  "perplexity_search",
56
59
  "perplexity_ask",
57
60
  "perplexity_research",
@@ -81,7 +84,9 @@
81
84
  "feedback_stats",
82
85
  "feedback_summary",
83
86
  "estimate_uncertainty",
84
- "report_product_issue"
87
+ "report_product_issue",
88
+ "require_evidence_for_claim",
89
+ "session_report"
85
90
  ],
86
91
  "commerce": [
87
92
  "capture_feedback",
@@ -129,6 +134,8 @@
129
134
  "describe_semantic_entity",
130
135
  "get_reliability_rules",
131
136
  "describe_reliability_entity",
137
+ "require_evidence_for_claim",
138
+ "session_report",
132
139
  "perplexity_search",
133
140
  "perplexity_ask"
134
141
  ],
@@ -158,6 +165,8 @@
158
165
  "describe_semantic_entity",
159
166
  "get_reliability_rules",
160
167
  "describe_reliability_entity",
168
+ "require_evidence_for_claim",
169
+ "session_report",
161
170
  "perplexity_search",
162
171
  "perplexity_ask"
163
172
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thumbgate",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "Self-improving agent governance: type thumbs-up or thumbs-down on any AI agent action. ThumbGate turns every mistake into a prevention rule and blocks the pattern from repeating. One thumbs-down, never again. 33 pre-action gates, budget enforcement, and self-protection for Claude Code, Cursor, Codex, Gemini CLI, and Amp.",
5
5
  "homepage": "https://thumbgate-production.up.railway.app",
6
6
  "repository": {
@@ -19,7 +19,6 @@
19
19
  ".claude-plugin/marketplace.json",
20
20
  ".claude-plugin/plugin.json",
21
21
  ".well-known/",
22
- "CHANGELOG.md",
23
22
  "LICENSE",
24
23
  "README.md",
25
24
  "adapters/amp/skills/thumbgate-feedback/SKILL.md",
@@ -49,6 +48,7 @@
49
48
  "scripts/agentic-data-pipeline.js",
50
49
  "scripts/analytics-report.js",
51
50
  "scripts/analytics-window.js",
51
+ "scripts/autonomous-workflow.js",
52
52
  "scripts/async-job-runner.js",
53
53
  "scripts/audit-trail.js",
54
54
  "scripts/auto-promote-gates.js",
@@ -173,6 +173,7 @@
173
173
  "scripts/slo-alert-engine.js",
174
174
  "scripts/spec-gate.js",
175
175
  "scripts/statusline-cache-path.js",
176
+ "scripts/statusline-context.js",
176
177
  "scripts/statusline-lesson.js",
177
178
  "scripts/statusline-links.js",
178
179
  "scripts/statusline-local-stats.js",
@@ -251,7 +252,10 @@
251
252
  "trace:eval": "node scripts/decision-trace.js eval",
252
253
  "social:reply-monitor": "node scripts/social-reply-monitor.js",
253
254
  "social:reply-monitor:dry": "node scripts/social-reply-monitor.js --dry-run",
254
- "test": "npm run test:schema && npm run test:loop && npm run test:dpo && npm run test:kto && npm run test:api && npm run test:proof && npm run test:e2e && npm run test:rlaif && npm run test:attribution && npm run test:quality && npm run test:intelligence && npm run test:training-export && npm run test:deployment && npm run test:operational-integrity && npm run test:workflow && npm run test:billing && npm run test:cli && npm run test:watcher && npm run test:autoresearch && npm run test:ops && npm run test:session-analyzer && npm run test:tessl && npm run test:gates && npm run test:evoskill && npm run test:gates-hardening && npm run test:workers && npm run test:social-analytics && npm run test:memalign && npm run test:xmemory-lite && npm run test:filesystem-search && npm run test:zernio && npm run test:platform-limits && npm run test:post-video && npm run test:post-everywhere-instagram && npm run test:obsidian-export && npm run test:lesson-db && npm run test:lesson-rotation && npm run test:memory-dedup && npm run test:feedback-quality && npm run test:sync-version && npm run test:check-congruence && npm run test:tool-registry && npm run test:feedback-to-rules && npm run test:memory-firewall && npm run test:belief-update && npm run test:hosted-config && npm run test:operational-summary && npm run test:operator-key-auth && npm run test:cloudflare-sandbox && npm run test:mcp-config && npm run test:plan-gate && npm run test:pulse && npm run test:semantic-layer && npm run test:data-pipeline && npm run test:optimize-context && npm run test:principle-extractor && npm run test:analytics-window && npm run test:funnel-analytics && npm run test:experiment-tracker && npm run test:build-metadata && npm run test:context-engine && npm run test:hf-papers && npm run test:marketing-experiment && npm run test:seo-gsd && npm run test:verify-run && npm run test:export-dpo-pairs && npm run test:export-hf-dataset && npm run test:license && npm run test:bot-detector && npm run test:postinstall && npm run test:funnel-invariants && npm run test:cli-telemetry && npm run test:pro-parity && npm run test:model-tier-router && npm run test:computer-use-firewall && npm run test:skill-exporter && npm run test:statusline && npm run test:evolution && npm run test:org-dashboard && npm run test:multi-hop-recall && npm run test:synthetic-dpo && npm run test:thumbgate-skill && npm run test:learn-hub && npm run test:feedback-fallback && npm run test:metaclaw && npm run test:server-lock && npm run test:control-tower && npm run test:pii-scanner && npm run test:data-governance && npm run test:lesson-inference && npm run test:semantic-dedup && npm run test:fs-utils && npm run test:cli-schema && npm run test:explore && npm run test:lesson-reranker && npm run test:lesson-retrieval && npm run test:cross-encoder && npm run test:reflector-agent && npm run test:feedback-session && npm run test:feedback-history-distiller && npm run test:hallucination-detector && npm run test:history-distiller && npm run test:predictive-insights && npm run test:prove-predictive-insights && npm run test:statusbar-cli && npm run test:generate-instagram-card && npm run test:instagram-thumbgate-post && npm run test:publish-instagram-thumbgate && npm run test:lesson-synthesis && npm run test:background-governance && npm run test:memory-migration && npm run test:prompt-dlp && npm run test:ephemeral-store && npm run test:agent-security && npm run test:skill-progressive && npm run test:per-step-scoring && npm run test:weekly-auto-post && npm run test:social-post-hourly && npm run test:social-quality-gate && npm run test:a2ui-engine && npm run test:gate-satisfy && npm run test:money-watcher && npm run test:budget && npm run test:quick-start && npm run test:utm && npm run test:product-feedback && npm run test:feedback-root-consolidator && npm run test:engagement-audit && npm run test:install-growth-automation && npm run test:publish-thumbgate-launch && npm run test:reconcile-thumbgate-campaign && npm run test:reddit-publisher && npm run test:schedule-thumbgate-campaign && npm run test:social-reply-monitor && npm run test:sync-launch-assets && npm run test:ai-search-visibility && npm run test:perplexity && npm run test:security-scanner && npm run test:llm-client && npm run test:managed-lesson-agent && npm run test:self-distill && npm run test:meta-agent && npm run test:harness-selector && npm run test:thumbgate-bench && npm run test:seo-guides && npm run test:enforcement-loop && npm run test:cli-agent-experience && npm run test:bot-detection && npm run test:checkout-bot-guard && npm run test:session-health && npm run test:session-episodes && npm run test:spec-gate && npm run test:decision-trace && npm run test:dashboard-insights && npm run test:prompt-eval && npm run test:demo-voiceover && npm run test:gate-coherence && npm run test:gate-eval && npm run test:high-roi && npm run test:public-static-assets && npm run test:token-savings && npm run test:workflow-gate-checkpoint && npm run test:lesson-export-import && npm run test:landing-page-claims && npm run test:dashboard-deeplink-e2e && npm run test:public-package-parity && npm run test:token-savings-dashboard && npm run test:cursor-wiring && npm run test:pretooluse-injection && npm run test:recent-corrective-context && npm run test:durability-step && npm run test:mailer && npm run test:brand-assets && npm run test:enforcement-teeth",
255
+ "test": "npm run test:schema && npm run test:loop && npm run test:dpo && npm run test:kto && npm run test:api && npm run test:proof && npm run test:e2e && npm run test:rlaif && npm run test:attribution && npm run test:quality && npm run test:intelligence && npm run test:training-export && npm run test:deployment && npm run test:operational-integrity && npm run test:workflow && npm run test:billing && npm run test:cli && npm run test:watcher && npm run test:autoresearch && npm run test:ops && npm run test:session-analyzer && npm run test:tessl && npm run test:gates && npm run test:evoskill && npm run test:gates-hardening && npm run test:workers && npm run test:social-analytics && npm run test:memalign && npm run test:xmemory-lite && npm run test:filesystem-search && npm run test:zernio && npm run test:platform-limits && npm run test:post-video && npm run test:post-everywhere-instagram && npm run test:obsidian-export && npm run test:lesson-db && npm run test:lesson-rotation && npm run test:memory-dedup && npm run test:feedback-quality && npm run test:sync-version && npm run test:check-congruence && npm run test:tool-registry && npm run test:feedback-to-rules && npm run test:memory-firewall && npm run test:belief-update && npm run test:hosted-config && npm run test:operational-summary && npm run test:operator-key-auth && npm run test:cloudflare-sandbox && npm run test:mcp-config && npm run test:plan-gate && npm run test:pulse && npm run test:semantic-layer && npm run test:data-pipeline && npm run test:optimize-context && npm run test:principle-extractor && npm run test:analytics-window && npm run test:funnel-analytics && npm run test:experiment-tracker && npm run test:build-metadata && npm run test:context-engine && npm run test:hf-papers && npm run test:marketing-experiment && npm run test:seo-gsd && npm run test:verify-run && npm run test:export-dpo-pairs && npm run test:export-hf-dataset && npm run test:license && npm run test:bot-detector && npm run test:postinstall && npm run test:funnel-invariants && npm run test:cli-telemetry && npm run test:pro-parity && npm run test:model-tier-router && npm run test:computer-use-firewall && npm run test:skill-exporter && npm run test:statusline && npm run test:evolution && npm run test:org-dashboard && npm run test:multi-hop-recall && npm run test:synthetic-dpo && npm run test:thumbgate-skill && npm run test:learn-hub && npm run test:feedback-fallback && npm run test:metaclaw && npm run test:server-lock && npm run test:control-tower && npm run test:pii-scanner && npm run test:data-governance && npm run test:lesson-inference && npm run test:semantic-dedup && npm run test:fs-utils && npm run test:cli-schema && npm run test:explore && npm run test:lesson-reranker && npm run test:lesson-retrieval && npm run test:cross-encoder && npm run test:reflector-agent && npm run test:feedback-session && npm run test:feedback-history-distiller && npm run test:hallucination-detector && npm run test:history-distiller && npm run test:predictive-insights && npm run test:prove-predictive-insights && npm run test:statusbar-cli && npm run test:generate-instagram-card && npm run test:instagram-thumbgate-post && npm run test:publish-instagram-thumbgate && npm run test:lesson-synthesis && npm run test:background-governance && npm run test:memory-migration && npm run test:prompt-dlp && npm run test:ephemeral-store && npm run test:agent-security && npm run test:skill-progressive && npm run test:per-step-scoring && npm run test:weekly-auto-post && npm run test:social-post-hourly && npm run test:social-quality-gate && npm run test:a2ui-engine && npm run test:gate-satisfy && npm run test:money-watcher && npm run test:budget && npm run test:quick-start && npm run test:utm && npm run test:product-feedback && npm run test:feedback-root-consolidator && npm run test:engagement-audit && npm run test:install-growth-automation && npm run test:publish-thumbgate-launch && npm run test:reconcile-thumbgate-campaign && npm run test:reddit-publisher && npm run test:schedule-thumbgate-campaign && npm run test:social-reply-monitor && npm run test:sync-launch-assets && npm run test:ai-search-visibility && npm run test:perplexity && npm run test:security-scanner && npm run test:llm-client && npm run test:managed-lesson-agent && npm run test:self-distill && npm run test:meta-agent && npm run test:harness-selector && npm run test:thumbgate-bench && npm run test:seo-guides && npm run test:enforcement-loop && npm run test:cli-agent-experience && npm run test:bot-detection && npm run test:checkout-bot-guard && npm run test:session-health && npm run test:session-episodes && npm run test:spec-gate && npm run test:decision-trace && npm run test:dashboard-insights && npm run test:prompt-eval && npm run test:demo-voiceover && npm run test:gate-coherence && npm run test:gate-eval && npm run test:high-roi && npm run test:public-static-assets && npm run test:token-savings && npm run test:workflow-gate-checkpoint && npm run test:lesson-export-import && npm run test:landing-page-claims && npm run test:dashboard-deeplink-e2e && npm run test:public-package-parity && npm run test:token-savings-dashboard && npm run test:cursor-wiring && npm run test:pretooluse-injection && npm run test:recent-corrective-context && npm run test:durability-step && npm run test:mailer && npm run test:brand-assets && npm run test:enforcement-teeth && npm run test:swarm-coordinator && npm run test:session-report && npm run test:require-evidence-gate",
256
+ "test:swarm-coordinator": "node --test tests/swarm-coordinator.test.js",
257
+ "test:session-report": "node --test tests/session-report.test.js",
258
+ "test:require-evidence-gate": "node --test tests/require-evidence-gate.test.js",
255
259
  "test:session-health": "node --test tests/session-health-sensor.test.js",
256
260
  "test:session-episodes": "node --test tests/session-episode-store.test.js",
257
261
  "test:spec-gate": "node --test tests/spec-gate.test.js",
@@ -265,7 +269,7 @@
265
269
  "test:multi-hop-recall": "node --test tests/multi-hop-recall.test.js",
266
270
  "test:synthetic-dpo": "node --test tests/synthetic-dpo.test.js",
267
271
  "test:thumbgate-skill": "node --test tests/thumbgate-skill.test.js",
268
- "test:statusline": "node --test tests/claude-feedback-sync.test.js tests/statusline.test.js tests/statusline-links.test.js",
272
+ "test:statusline": "node --test tests/claude-feedback-sync.test.js tests/statusline.test.js tests/statusline-context.test.js tests/statusline-links.test.js",
269
273
  "test:memory-dedup": "node --test tests/memory-dedup.test.js",
270
274
  "test:lesson-db": "node --test tests/lesson-db.test.js",
271
275
  "test:lesson-rotation": "node --test tests/lesson-rotation.test.js",
@@ -483,10 +487,11 @@
483
487
  "test:demo-voiceover": "node --test tests/demo-voiceover.test.js",
484
488
  "test:gate-coherence": "node --test tests/gate-coherence.test.js",
485
489
  "test:gate-eval": "node --test tests/gate-eval.test.js",
486
- "test:high-roi": "node --test tests/high-roi.test.js",
490
+ "test:high-roi": "node --test tests/high-roi.test.js tests/autonomous-workflow.test.js",
487
491
  "test:public-static-assets": "node --test tests/public-static-assets.test.js",
488
492
  "test:token-savings": "node --test tests/token-savings.test.js",
489
- "test:workflow-gate-checkpoint": "node --test tests/workflow-gate-checkpoint.test.js",
493
+ "test:workflow-gate-checkpoint": "node --test tests/workflow-gate-checkpoint.test.js tests/autonomous-workflow.test.js",
494
+ "workflow:autonomous": "node scripts/autonomous-workflow.js",
490
495
  "test:lesson-export-import": "node --test tests/lesson-export-import.test.js",
491
496
  "test:landing-page-claims": "node --test tests/landing-page-claims.test.js",
492
497
  "test:dashboard-deeplink-e2e": "node --test tests/dashboard-deeplink-e2e.test.js",
package/public/index.html CHANGED
@@ -974,7 +974,7 @@ __GA_BOOTSTRAP__
974
974
  <!-- HOW IT WORKS -->
975
975
  <section class="how-it-works" id="how-it-works">
976
976
  <div class="container">
977
- <div class="section-label">New in v1.7.0</div>
977
+ <div class="section-label">New in v1.8.0</div>
978
978
  <h2 class="section-title">Three steps to stop repeated AI failures</h2>
979
979
  <div class="steps">
980
980
  <div class="step">
@@ -1330,7 +1330,7 @@ __GA_BOOTSTRAP__
1330
1330
  <a href="https://www.linkedin.com/in/igorganapolsky" target="_blank" rel="noopener">LinkedIn</a>
1331
1331
  <a href="/blog">Blog</a>
1332
1332
  </div>
1333
- <span class="footer-copy">© 2026 Max Smith KDP LLC · MIT License · v1.7.0</span>
1333
+ <span class="footer-copy">© 2026 Max Smith KDP LLC · MIT License · v1.8.0</span>
1334
1334
  </div>
1335
1335
  </footer>
1336
1336
 
@@ -0,0 +1,377 @@
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
+ };