mindforge-cc 11.9.1 → 11.9.3

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 (121) hide show
  1. package/.agent/CLAUDE.md +37 -13
  2. package/.agent/hooks/mindforge-block-no-verify.js +61 -13
  3. package/.agent/hooks/mindforge-config-protection.js +82 -3
  4. package/.agent/hooks/mindforge-context-monitor.js +1 -1
  5. package/.agent/hooks/mindforge-workflow-guard.js +2 -2
  6. package/.agent/hooks/run-with-flags.js +190 -20
  7. package/.agent/mindforge/browse.md +2 -2
  8. package/.agent/mindforge/checkpoint.md +1 -1
  9. package/.agent/mindforge/consult.md +1 -1
  10. package/.agent/mindforge/cost-report.md +1 -1
  11. package/.agent/mindforge/harness-audit.md +1 -1
  12. package/.agent/mindforge/orch-add-feature.md +1 -1
  13. package/.agent/mindforge/orch-build-mvp.md +1 -1
  14. package/.agent/mindforge/orch-change-feature.md +1 -1
  15. package/.agent/mindforge/orch-fix-defect.md +1 -1
  16. package/.agent/mindforge/orch-refine-code.md +1 -1
  17. package/.agent/mindforge/qa.md +2 -2
  18. package/.claude/CLAUDE.md +37 -13
  19. package/.claude/commands/mindforge/browse.md +2 -2
  20. package/.claude/commands/mindforge/checkpoint.md +1 -1
  21. package/.claude/commands/mindforge/consult.md +1 -1
  22. package/.claude/commands/mindforge/cost-report.md +1 -1
  23. package/.claude/commands/mindforge/harness-audit.md +1 -1
  24. package/.claude/commands/mindforge/orch-add-feature.md +1 -1
  25. package/.claude/commands/mindforge/orch-build-mvp.md +1 -1
  26. package/.claude/commands/mindforge/orch-change-feature.md +1 -1
  27. package/.claude/commands/mindforge/orch-fix-defect.md +1 -1
  28. package/.claude/commands/mindforge/orch-refine-code.md +1 -1
  29. package/.claude/commands/mindforge/qa.md +2 -2
  30. package/.mindforge/MINDFORGE-SCHEMA.json +126 -13
  31. package/.mindforge/config.json +4 -4
  32. package/.mindforge/engine/autonomous/headless-adapter.md +2 -2
  33. package/.mindforge/engine/cost-tracking/router.md +1 -1
  34. package/.mindforge/engine/cost-tracking/token-ledger.md +21 -24
  35. package/.mindforge/engine/temporal-protocol.md +2 -2
  36. package/.mindforge/governance/change-classifier.md +20 -4
  37. package/.mindforge/memory/sync-manifest.json +1 -1
  38. package/.mindforge/metrics/METRICS-SCHEMA.md +13 -4
  39. package/.mindforge/personas/cost-optimizer.md +2 -2
  40. package/.mindforge/personas/multi-model-bridge.md +1 -1
  41. package/.mindforge/skills/agent-architecture-audit/SKILL.md +2 -2
  42. package/.mindforge/skills/cost-aware-routing/SKILL.md +3 -3
  43. package/.mindforge/skills/multi-llm-consult/SKILL.md +2 -2
  44. package/.mindforge/skills/orch-pipeline/SKILL.md +4 -4
  45. package/CHANGELOG.md +402 -0
  46. package/MINDFORGE.md +13 -6
  47. package/README.md +51 -2
  48. package/RELEASENOTES.md +55 -2
  49. package/SECURITY.md +22 -3
  50. package/bin/autonomous/audit-writer.js +48 -33
  51. package/bin/autonomous/auto-runner.js +65 -2
  52. package/bin/change-classifier.js +151 -16
  53. package/bin/dashboard/api-router.js +28 -47
  54. package/bin/dashboard/error-response.js +44 -0
  55. package/bin/dashboard/frontend/app.js +429 -0
  56. package/bin/dashboard/frontend/index.html +14 -390
  57. package/bin/dashboard/metrics-aggregator.js +75 -30
  58. package/bin/dashboard/revops-api.js +12 -2
  59. package/bin/dashboard/server.js +245 -6
  60. package/bin/dashboard/sse-bridge.js +11 -8
  61. package/bin/dashboard/temporal-api.js +11 -5
  62. package/bin/engine/remediation-engine.js +12 -1
  63. package/bin/engine/sre-manager.js +1 -1
  64. package/bin/engine/temporal-cli.js +56 -6
  65. package/bin/engine/temporal-hub.js +41 -9
  66. package/bin/engine/verification-runner.js +134 -17
  67. package/bin/engine/verify-cli.js +25 -7
  68. package/bin/eval/eval-harness.js +212 -1
  69. package/bin/eval/golden-set-retrieval.json +9 -0
  70. package/bin/governance/approval-record.js +147 -0
  71. package/bin/governance/approve.js +12 -7
  72. package/bin/governance/policy-engine.js +41 -3
  73. package/bin/governance/policy-gate-hardened.js +36 -1
  74. package/bin/governance/verify-approvals.js +163 -0
  75. package/bin/harness-audit.js +224 -10
  76. package/bin/hindsight-injector.js +8 -2
  77. package/bin/hooks/instinct-capture-hook.js +19 -5
  78. package/bin/install.js +63 -3
  79. package/bin/installer/harness-adapter-compliance.js +339 -28
  80. package/bin/installer/hook-registration.js +504 -0
  81. package/bin/installer-core.js +451 -63
  82. package/bin/learning/instinct-cli.js +14 -24
  83. package/bin/memory/knowledge-capture.js +23 -3
  84. package/bin/memory/knowledge-graph.js +70 -31
  85. package/bin/memory/vector-hub.js +500 -44
  86. package/bin/migrations/0.6.0-to-1.0.0.js +30 -25
  87. package/bin/migrations/1.0.0-to-2.0.0.js +22 -23
  88. package/bin/mindforge-cli.js +110 -17
  89. package/bin/models/cost-tracker.js +126 -29
  90. package/bin/models/model-client.js +6 -1
  91. package/bin/models/model-router.js +28 -7
  92. package/bin/models/usage-record.js +71 -0
  93. package/bin/revops/debt-monitor.js +57 -13
  94. package/bin/security/trust-gate-hook.js +50 -6
  95. package/bin/skill-validator.js +6 -1
  96. package/bin/skills-builder/skill-scorer.js +46 -6
  97. package/bin/updater/self-update.js +6 -1
  98. package/bin/updater/version-comparator.js +21 -1
  99. package/bin/utils/file-lock.js +106 -0
  100. package/bin/utils/mindforge-params.js +124 -0
  101. package/bin/utils/mindforge-version.js +99 -0
  102. package/bin/utils/redact-secrets.js +106 -0
  103. package/bin/validate-config.js +75 -17
  104. package/bin/wizard/setup-wizard.js +4 -1
  105. package/bin/wizard/theme.js +9 -1
  106. package/changelogs/index.json +11 -9
  107. package/changelogs/v11.9.2.md +209 -0
  108. package/changelogs/v11.9.3.md +195 -0
  109. package/docs/References/config-reference.md +76 -14
  110. package/docs/References/sdk-api.md +1 -1
  111. package/docs/Templates/Codebase/architecture.md +1 -1
  112. package/docs/commands-reference.md +4 -5
  113. package/docs/faq.md +25 -5
  114. package/docs/getting-started.md +3 -3
  115. package/docs/sdk-reference.md +15 -7
  116. package/docs/troubleshooting.md +10 -6
  117. package/docs/user-guide.md +14 -14
  118. package/examples/sdk-integration/README.md +1 -1
  119. package/package.json +10 -4
  120. package/subagents/.claude-plugin/marketplace.json +1 -1
  121. package/bin/dashboard/approval-handler.js +0 -136
@@ -8,9 +8,8 @@
8
8
 
9
9
  const fs = require('fs');
10
10
  const path = require('path');
11
- const os = require('os');
12
- const crypto = require('crypto');
13
11
  const { execFileSync } = require('child_process');
12
+ const { checksumRecord, expiryFrom, SCHEMA } = require('./approval-record');
14
13
 
15
14
  const ROOT = path.resolve(__dirname, '../../');
16
15
  const APPROVALS_DIR = path.join(ROOT, '.planning/approvals');
@@ -90,21 +89,27 @@ function approve(opts = {}) {
90
89
  // Verify approver identity — THROWS fail-closed if unverifiable (before any write).
91
90
  const identityVerification = verifyApproverIdentity(approver);
92
91
 
93
- const signature = crypto.createHash('sha256')
94
- .update(`${id}:${reason}:${timestamp}:${os.hostname()}`)
95
- .digest('hex');
96
-
92
+ // The old `signature` field was sha256(id:reason:timestamp:os.hostname()). It was not a
93
+ // signature (nothing signs it), it could not be verified by anyone holding the record
94
+ // (hostname is not a field of it), and the only consumer asserted its PRESENCE. Replaced by a
95
+ // recomputable checksum over the record's own fields — see bin/governance/approval-record.js.
96
+ //
97
+ // `expires_at` is new and load-bearing: without it, the record committed on 2026-06-11 for
98
+ // version 11.5.1 still satisfied the Tier-3 gate 67 days and 286 commits later, against
99
+ // 11.9.2. An acknowledgement now stops applying, and it does not carry to a later release.
97
100
  const record = {
101
+ schema: SCHEMA,
98
102
  id,
99
103
  project: pkg.name,
100
104
  version: pkg.version,
101
105
  tier: 3,
102
106
  approved_by: approver,
103
107
  timestamp,
108
+ expires_at: expiryFrom(timestamp),
104
109
  reason,
105
- signature: `sha256:${signature}`,
106
110
  identity_verification: identityVerification
107
111
  };
112
+ record.record_checksum = checksumRecord(record);
108
113
 
109
114
  if (!fs.existsSync(approvalsDir)) fs.mkdirSync(approvalsDir, { recursive: true });
110
115
  const filename = `approval-${id.toLowerCase()}.json`;
@@ -74,7 +74,17 @@ class PolicyEngine {
74
74
 
75
75
  // [PQAS] v7: Hardened Biometric Bypass for Risk > 95
76
76
  if (impactScore > 95) {
77
- const gateResult = await policyGate.evaluateBypass(intent, impactScore);
77
+ // The gate looks up the attestation by `intent.requestId`, which this method never
78
+ // set — it generated `requestId` as a local and passed the bare intent, so the lookup
79
+ // always received undefined. Measured: orbital-guardian returns
80
+ // {verified:false, reason:"missing requestId"}, so the attestation path could NEVER
81
+ // approve, even immediately after a successful recordBypass(). The gate was stuck
82
+ // closed, which is safe but means the whole hardware-attestation feature only ever
83
+ // denied. tests/v8-orbital-governance.test.js passes an object that DOES carry
84
+ // requestId, which is why the contract looked satisfied.
85
+ //
86
+ // A new object rather than a mutation: the caller's intent is not ours to modify.
87
+ const gateResult = await policyGate.evaluateBypass({ ...intent, requestId }, impactScore);
78
88
  if (gateResult.status === 'WAIT_FOR_BIOMETRIC' || gateResult.status === 'WAIT_FOR_ORBITAL') {
79
89
  verdict = {
80
90
  verdict: 'DENY',
@@ -86,8 +96,28 @@ class PolicyEngine {
86
96
  this.logAudit(intent, impactScore, verdict);
87
97
  return verdict;
88
98
  }
89
- console.log(`[PQAS-GATE] [${requestId}] Biometric signature verified. Proceeding with high-risk mutation.`);
90
- verdict = { verdict: 'PERMIT', reason: `Authorized via Biometric Bypass [${gateResult.signature || 'WEB-AUTHN-DEX'}]`, requestId };
99
+ // Only claim an attestation when one was actually verified. evaluateBypass has TWO
100
+ // non-WAIT returns, and they mean opposite things: an attestation verified in the
101
+ // enclave (carries attestation_id), or "Impact within standard threshold" — which
102
+ // states that no attestation was required. The old code logged "Biometric signature
103
+ // verified" and recorded the audit reason "Authorized via Biometric Bypass
104
+ // [WEB-AUTHN-DEX]" for BOTH, so the audit trail would assert a biometric verification
105
+ // for an operation the gate had explicitly waved through unchecked. That is the audit
106
+ // equivalent of a gate printing success without running.
107
+ const attested = Boolean(gateResult.attestation_id);
108
+ if (attested) {
109
+ console.log(`[PQAS-GATE] [${requestId}] Hardware attestation ${gateResult.attestation_id} verified. Proceeding with high-risk mutation.`);
110
+ } else {
111
+ console.log(`[PQAS-GATE] [${requestId}] Gate returned ${gateResult.status} without an attestation: ${gateResult.reason}. NOT recording this as attested.`);
112
+ }
113
+ verdict = {
114
+ verdict: 'PERMIT',
115
+ reason: attested
116
+ ? `Authorized via hardware attestation [${gateResult.attestation_id}]`
117
+ : `Permitted without attestation: ${gateResult.reason || gateResult.status}`,
118
+ requestId,
119
+ attested,
120
+ };
91
121
  this.logAudit(intent, impactScore, verdict);
92
122
  return verdict;
93
123
  }
@@ -165,6 +195,12 @@ class PolicyEngine {
165
195
  }
166
196
 
167
197
  logAudit(intent, impactScore, verdict) {
198
+ // LOCK-01: AuditWriter.write -> appendAuditEntrySync now takes a FAIL-CLOSED lock,
199
+ // so this can reject under contention. It is intentionally not awaited (the verdict
200
+ // path is synchronous), so catch here — otherwise the rejection escapes to a global
201
+ // unhandledRejection handler and the lost audit record is invisible at this site.
202
+ // The verdict is still returned: promoting an audit-write failure to an implicit
203
+ // DENY is a behaviour change for v12, not a patch.
168
204
  this._auditWriter.write({
169
205
  timestamp: new Date().toISOString(),
170
206
  requestId: verdict.requestId,
@@ -175,6 +211,8 @@ class PolicyEngine {
175
211
  impactScore,
176
212
  verdict: verdict.verdict,
177
213
  reason: verdict.reason
214
+ }).catch(err => {
215
+ console.error(`[APO-AUDIT-FAIL] [${verdict.requestId}] RISK-AUDIT append failed — decision NOT recorded: ${err.message}`);
178
216
  });
179
217
  }
180
218
 
@@ -12,7 +12,42 @@ const configManager = require('../governance/config-manager');
12
12
  class PolicyGateHardened {
13
13
  constructor() {
14
14
  // bypasses.json deprecated in favor of orbital.attestations table (v8)
15
- this.criticalThreshold = configManager.get('governance.critical_drift_threshold', 95);
15
+ // Impact scores are 0-100. This used to read `governance.critical_drift_threshold` with a
16
+ // default of 95 — but that key is a DRIFT RATIO, read by bin/engine/logic-drift-detector.js:23
17
+ // with a default of 0.50, and the shipped config sets it to 0.5. One key, two incompatible
18
+ // scales: the drift detector was correct and this gate silently received 0.5 where it expected
19
+ // ~95, making the "within standard threshold" branch below permanently dead.
20
+ //
21
+ // Dead was the SAFE direction, but it is one plausible edit from the unsafe one: raising that
22
+ // key to 95 to "fix" this gate would return ALLOWED for every high-risk score AND break drift
23
+ // detection, and policy-engine.js would then log "Biometric signature verified" for an
24
+ // operation where no attestation was ever attempted. Hence a dedicated key, plus a range guard
25
+ // so a ratio can never be mistaken for a score again.
26
+ this.criticalThreshold = this.resolveImpactThreshold();
27
+ }
28
+
29
+ /**
30
+ * The impact score (0-100) at or below which no hardware attestation is demanded.
31
+ *
32
+ * Rejects anything that is not on the 0-100 scale rather than trusting it. A value of 0.5 is a
33
+ * ratio that somebody has pointed at the wrong knob, and silently accepting it is how this gate
34
+ * came to compare a drift ratio against an impact score.
35
+ *
36
+ * @returns {number}
37
+ */
38
+ resolveImpactThreshold(fallback = 95) {
39
+ const raw = configManager.get('governance.critical_impact_threshold', fallback);
40
+ const value = Number(raw);
41
+ if (!Number.isFinite(value) || value < 1 || value > 100) {
42
+ // Deliberately loud: a mis-scaled threshold changes whether high-risk mutations demand
43
+ // attestation at all, so it must not be absorbed quietly.
44
+ console.warn(
45
+ `[ORBITAL-GATE] governance.critical_impact_threshold=${JSON.stringify(raw)} is not an ` +
46
+ 'impact score on the 0-100 scale (a value below 1 is usually a drift RATIO pointed at the ' +
47
+ `wrong key). Using ${fallback}.`);
48
+ return fallback;
49
+ }
50
+ return value;
16
51
  }
17
52
 
18
53
  /**
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ /**
4
+ * Verify the integrity of every committed approval record, and disclose Tier-3 changes.
5
+ *
6
+ * Usage:
7
+ * node bin/governance/verify-approvals.js [--tier N] [--dir <path>] [--json]
8
+ *
9
+ * Exit 0 = every record is intact and current (or there are none).
10
+ * Exit 1 = at least one record is malformed, tampered with, or stale.
11
+ *
12
+ * WHAT REPLACED WHAT, and why the contract changed.
13
+ *
14
+ * The previous gate lived inline in control-plane.yml and asked: "does .planning/approvals/
15
+ * contain at least one file that clears an identity bar?" Measured, that question has three
16
+ * fatal properties:
17
+ *
18
+ * * It is a pure function of the DIRECTORY, not of the change. Running the identical script in
19
+ * a temp dir containing only the committed approval — with no git repository at all — exited
20
+ * 0 with the same success message.
21
+ * * It therefore granted approval in perpetuity. One record, committed 2026-06-11 for version
22
+ * 11.5.1, satisfied it for 67 days and 286 commits, against 11.9.2.
23
+ * * It was committed in c6ec9a9 — the SAME commit that relaxed the gate to accept it.
24
+ *
25
+ * So the old step could not distinguish an approved change from an unapproved one. This script
26
+ * does not try to fix that by making the file mean more, because a git-tracked file cannot carry
27
+ * authorization: anyone who can push can write one, and MINDFORGE_ALLOW_UNVERIFIED_APPROVAL=1
28
+ * makes minting a one-liner. Verified live: before branch protection was enabled,
29
+ * `branches/{main,develop}/protection` returned 404 and `rulesets` was empty, so nothing stood
30
+ * behind the gate at all.
31
+ *
32
+ * The honest split, and what each half is worth:
33
+ *
34
+ * INTEGRITY (enforced here, can fail). Records must be well-formed, checksum-consistent,
35
+ * honestly marked as to identity, bound to the release being built, and unexpired. This is
36
+ * what stops a record being edited after the fact or recycled across releases, and it runs on
37
+ * EVERY build rather than only Tier-3 ones — a corrupt record is corrupt regardless of tier.
38
+ *
39
+ * AUTHORIZATION (not enforced here, deliberately). Enforced by branch protection: a required
40
+ * pull request plus required status checks on main and develop. On a Tier-3 change with no
41
+ * current acknowledgement this script DISCLOSES loudly and exits 0, rather than blocking. That
42
+ * is a deliberate choice: 28% of recent commits touch the trust surface, and blocking them
43
+ * behind a self-issued file would add friction proportional to nothing. Overstating a check is
44
+ * how the previous gate came to be trusted for two months while proving nothing.
45
+ */
46
+
47
+ const fs = require('fs');
48
+ const path = require('path');
49
+ const { verifyRecord } = require('./approval-record');
50
+
51
+ const ROOT = path.resolve(__dirname, '..', '..');
52
+
53
+ function parseArgs(argv) {
54
+ const out = { tier: null, dir: path.join(ROOT, '.planning', 'approvals'), json: false };
55
+ for (let i = 0; i < argv.length; i++) {
56
+ if (argv[i] === '--tier') out.tier = String(argv[++i] ?? '').trim();
57
+ else if (argv[i] === '--dir') out.dir = argv[++i];
58
+ else if (argv[i] === '--json') out.json = true;
59
+ }
60
+ return out;
61
+ }
62
+
63
+ /** GitHub Actions annotations when running there; plain text otherwise. */
64
+ const inActions = () => Boolean(process.env.GITHUB_ACTIONS);
65
+ function notice(msg) { console.log(inActions() ? `::notice::${msg}` : msg); }
66
+ function error(msg) { console.error(inActions() ? `::error::${msg}` : msg); }
67
+ function summary(lines) {
68
+ const f = process.env.GITHUB_STEP_SUMMARY;
69
+ if (!f) return;
70
+ try { fs.appendFileSync(f, `${lines.join('\n')}\n`); } catch { /* summary is best-effort */ }
71
+ }
72
+
73
+ function main(argv = process.argv.slice(2)) {
74
+ const args = parseArgs(argv);
75
+ const currentVersion = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8')).version;
76
+
77
+ let files = [];
78
+ try {
79
+ files = fs.readdirSync(args.dir).filter((f) => f.endsWith('.json'));
80
+ } catch {
81
+ files = []; // an absent directory is not an error — it means no acknowledgements exist
82
+ }
83
+
84
+ const results = [];
85
+ for (const f of files) {
86
+ const p = path.join(args.dir, f);
87
+ let rec;
88
+ try { rec = JSON.parse(fs.readFileSync(p, 'utf8')); }
89
+ catch (e) {
90
+ results.push({ file: f, ok: false, stale: false, problems: [`not valid JSON: ${e.message}`] });
91
+ continue;
92
+ }
93
+ results.push({ file: f, ...verifyRecord(rec, { currentVersion }) });
94
+ }
95
+
96
+ const broken = results.filter((r) => !r.ok);
97
+ const valid = results.filter((r) => r.ok);
98
+
99
+ if (args.json) {
100
+ console.log(JSON.stringify({ currentVersion, tier: args.tier, results }, null, 2));
101
+ } else {
102
+ for (const r of broken) {
103
+ error(`approval ${r.file} is not usable:`);
104
+ for (const p of r.problems) console.error(` - ${p}`);
105
+ }
106
+ for (const r of valid) notice(`approval ${r.file} is intact and current (${currentVersion}).`);
107
+ }
108
+
109
+ if (broken.length) {
110
+ error(`${broken.length} approval record(s) rejected. Remove the stale file, or re-mint with ` +
111
+ '`node bin/governance/approve.js "<reason>"`. A record does not carry forward to a later release.');
112
+ summary([
113
+ '## 🔴 Approval record integrity failed',
114
+ '',
115
+ ...broken.flatMap((r) => [`**${r.file}**`, ...r.problems.map((p) => `- ${p}`), '']),
116
+ ]);
117
+ return 1;
118
+ }
119
+
120
+ if (!results.length) notice('No approval records present — nothing to verify.');
121
+
122
+ // Tier-3 disclosure. Never blocks — see the header.
123
+ if (args.tier === '3') {
124
+ if (valid.length) {
125
+ notice(`Tier 3 change with ${valid.length} current acknowledgement(s) on record.`);
126
+ summary([
127
+ '## ⚖️ Tier 3 change — acknowledged',
128
+ '',
129
+ ...valid.map((r) => `- \`${r.file}\``),
130
+ '',
131
+ 'Recorded acknowledgement only. Authorization is enforced by branch protection ' +
132
+ '(required PR + required checks), not by this file.',
133
+ ]);
134
+ } else {
135
+ notice('Tier 3 change with NO recorded acknowledgement. Not blocking — see the summary.');
136
+ summary([
137
+ '## ⚖️ Tier 3 change — touches the trust surface',
138
+ '',
139
+ 'This change modifies a sensitive path (auth/payment/security, `bin/governance/`, ',
140
+ '`bin/security/`, hook registration, workflows or the approval records themselves) ',
141
+ 'and there is **no current acknowledgement** in `.planning/approvals/`.',
142
+ '',
143
+ '**This check does not block it, on purpose.** A git-tracked approval file cannot carry ',
144
+ 'authorization: anyone who can push can write one.',
145
+ '',
146
+ 'Authorization comes from branch protection — a required pull request plus required ',
147
+ 'status checks on `main` and `develop`.',
148
+ '',
149
+ 'To record a human acknowledgement (integrity-checked, 72h, bound to this release):',
150
+ '',
151
+ '```bash',
152
+ 'node bin/governance/approve.js "what you reviewed and why it is safe"',
153
+ '```',
154
+ ]);
155
+ }
156
+ }
157
+
158
+ return 0;
159
+ }
160
+
161
+ module.exports = { main, parseArgs };
162
+
163
+ if (require.main === module) process.exit(main());