@smartmemory/compose 0.1.43-beta → 0.2.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.
Files changed (76) hide show
  1. package/bin/compose.js +179 -35
  2. package/dist/assets/App-VU2lfA8m.js +770 -0
  3. package/dist/assets/{arc-N74_SuiS.js → arc-CIeqpX37.js} +1 -1
  4. package/dist/assets/{architectureDiagram-3BPJPVTR-DHOb5xas.js → architectureDiagram-3BPJPVTR-itmOSZLE.js} +1 -1
  5. package/dist/assets/{blockDiagram-GPEHLZMM-DR9b_xXC.js → blockDiagram-GPEHLZMM-N7MotI_5.js} +8 -8
  6. package/dist/assets/{c4Diagram-AAUBKEIU-EXIx4J1v.js → c4Diagram-AAUBKEIU-DRKW39LH.js} +1 -1
  7. package/dist/assets/channel-DugSMLKi.js +1 -0
  8. package/dist/assets/{chunk-2J33WTMH-CyZqa6ub.js → chunk-2J33WTMH-CF6iSwEb.js} +1 -1
  9. package/dist/assets/{chunk-4BX2VUAB-SjPmvNaj.js → chunk-4BX2VUAB-BTe-QE0R.js} +1 -1
  10. package/dist/assets/{chunk-55IACEB6-Cnu3mdms.js → chunk-55IACEB6-E2hHEsl9.js} +1 -1
  11. package/dist/assets/{chunk-727SXJPM-DNj5i6fj.js → chunk-727SXJPM-CBRmkSvh.js} +1 -1
  12. package/dist/assets/{chunk-AQP2D5EJ-BIVskOlI.js → chunk-AQP2D5EJ-BdtQ63fN.js} +1 -1
  13. package/dist/assets/{chunk-FMBD7UC4-BWlLU-hh.js → chunk-FMBD7UC4-DfYQ2YmB.js} +1 -1
  14. package/dist/assets/{chunk-ND2GUHAM-DQEknadH.js → chunk-ND2GUHAM-CDrOVOW5.js} +1 -1
  15. package/dist/assets/{chunk-QZHKN3VN-CUYnhnAB.js → chunk-QZHKN3VN-DwjqJ9xB.js} +1 -1
  16. package/dist/assets/classDiagram-4FO5ZUOK-D2RRwp7J.js +1 -0
  17. package/dist/assets/classDiagram-v2-Q7XG4LA2-D2RRwp7J.js +1 -0
  18. package/dist/assets/{cose-bilkent-S5V4N54A-X3n23b12.js → cose-bilkent-S5V4N54A-MHpsrtBZ.js} +1 -1
  19. package/dist/assets/{dagre-BM42HDAG-C0SrhQ_X.js → dagre-BM42HDAG-DaPz_mPt.js} +1 -1
  20. package/dist/assets/{diagram-2AECGRRQ-Bc3qx6pJ.js → diagram-2AECGRRQ-DIdstuOm.js} +1 -1
  21. package/dist/assets/{diagram-5GNKFQAL-UiCrD06F.js → diagram-5GNKFQAL-DbkTGVES.js} +1 -1
  22. package/dist/assets/{diagram-KO2AKTUF-B9Vn5KyO.js → diagram-KO2AKTUF-BPalYJed.js} +1 -1
  23. package/dist/assets/{diagram-LMA3HP47-DLOYeLM3.js → diagram-LMA3HP47-vnySSoyd.js} +1 -1
  24. package/dist/assets/{diagram-OG6HWLK6-CXjh2miZ.js → diagram-OG6HWLK6-Dv3BUJft.js} +1 -1
  25. package/dist/assets/{erDiagram-TEJ5UH35-EmDzXNsM.js → erDiagram-TEJ5UH35-B3OLgtKK.js} +1 -1
  26. package/dist/assets/{flowDiagram-I6XJVG4X-vk6E_ebo.js → flowDiagram-I6XJVG4X-DdpxVf-5.js} +1 -1
  27. package/dist/assets/{ganttDiagram-6RSMTGT7-DYYSAjNx.js → ganttDiagram-6RSMTGT7-QALT_Lj9.js} +4 -4
  28. package/dist/assets/{gitGraphDiagram-PVQCEYII-CWPZVbhV.js → gitGraphDiagram-PVQCEYII-nITcPPED.js} +1 -1
  29. package/dist/assets/{graph-uO5hwVZK.js → graph-DnLKqSPg.js} +2 -2
  30. package/dist/assets/{index-BYYTTzUT.js → index-CLb8RFcn.js} +3 -3
  31. package/dist/assets/index-jqUffYBL.css +1 -0
  32. package/dist/assets/{infoDiagram-5YYISTIA-Dsu-eeJm.js → infoDiagram-5YYISTIA-CjlRce3x.js} +1 -1
  33. package/dist/assets/{ishikawaDiagram-YF4QCWOH-BP1SP8WA.js → ishikawaDiagram-YF4QCWOH-OyKVgxOz.js} +1 -1
  34. package/dist/assets/{journeyDiagram-JHISSGLW-DkE5By_R.js → journeyDiagram-JHISSGLW-3FaFyfLR.js} +1 -1
  35. package/dist/assets/{kanban-definition-UN3LZRKU-Cf_230xs.js → kanban-definition-UN3LZRKU-DUPnRo3q.js} +1 -1
  36. package/dist/assets/{linear-B-paxRBQ.js → linear-BeL8i3rv.js} +1 -1
  37. package/dist/assets/{mindmap-definition-RKZ34NQL-DAp6uJ_b.js → mindmap-definition-RKZ34NQL-C0CwWNdR.js} +1 -1
  38. package/dist/assets/mobile-qvdJ5p0m.js +17 -0
  39. package/dist/assets/{pieDiagram-4H26LBE5-CbYY5KL0.js → pieDiagram-4H26LBE5-DaU2jPjX.js} +1 -1
  40. package/dist/assets/{quadrantDiagram-W4KKPZXB-D5S4_ac5.js → quadrantDiagram-W4KKPZXB-HFtjZSAT.js} +1 -1
  41. package/dist/assets/{requirementDiagram-4Y6WPE33-BrPWCnHz.js → requirementDiagram-4Y6WPE33-CX_Mz3gv.js} +1 -1
  42. package/dist/assets/{sankeyDiagram-5OEKKPKP-CP8j1mcl.js → sankeyDiagram-5OEKKPKP-BR2_eTy9.js} +1 -1
  43. package/dist/assets/{sequenceDiagram-3UESZ5HK-c8DuhvUj.js → sequenceDiagram-3UESZ5HK-CtHp0Qnp.js} +1 -1
  44. package/dist/assets/{stateDiagram-AJRCARHV-KO9G1Jrm.js → stateDiagram-AJRCARHV-DmiEmD6G.js} +1 -1
  45. package/dist/assets/stateDiagram-v2-BHNVJYJU-7rdO1Tgp.js +1 -0
  46. package/dist/assets/{timeline-definition-PNZ67QCA-Cs2HLlbG.js → timeline-definition-PNZ67QCA-GSHqrJ3A.js} +1 -1
  47. package/dist/assets/{vennDiagram-CIIHVFJN-rcSRidqI.js → vennDiagram-CIIHVFJN-CNxhQnCU.js} +1 -1
  48. package/dist/assets/{wardley-L42UT6IY-BsajGfii.js → wardley-L42UT6IY-Bf-gQIFY.js} +1 -1
  49. package/dist/assets/{wardleyDiagram-YWT4CUSO-CSWALc_m.js → wardleyDiagram-YWT4CUSO-RGxoapr7.js} +1 -1
  50. package/dist/assets/{xychartDiagram-2RQKCTM6-jC4Q0GvG.js → xychartDiagram-2RQKCTM6-1_H1qVde.js} +1 -1
  51. package/dist/index.html +3 -3
  52. package/lib/build.js +3 -2
  53. package/lib/feature-code.js +14 -4
  54. package/lib/feature-json.js +33 -2
  55. package/lib/feature-validator.js +135 -11
  56. package/lib/feature-writer.js +83 -3
  57. package/lib/migrate-roadmap.js +16 -2
  58. package/lib/project-paths.js +16 -0
  59. package/lib/roadmap-config.js +50 -0
  60. package/lib/roadmap-gen.js +46 -31
  61. package/lib/roadmap-heading.js +85 -0
  62. package/lib/roadmap-parser.js +69 -18
  63. package/lib/roadmap-preservers.js +60 -19
  64. package/lib/roadmap-roundtrip.js +137 -0
  65. package/lib/vision-writer.js +42 -14
  66. package/lib/xref-sync.js +160 -0
  67. package/package.json +1 -1
  68. package/server/compose-mcp.js +2 -1
  69. package/server/vision-store.js +1 -1
  70. package/dist/assets/App-CdP799CF.js +0 -768
  71. package/dist/assets/channel-yPY0IE15.js +0 -1
  72. package/dist/assets/classDiagram-4FO5ZUOK-SGKYXTP4.js +0 -1
  73. package/dist/assets/classDiagram-v2-Q7XG4LA2-SGKYXTP4.js +0 -1
  74. package/dist/assets/index-Dh2rRpBR.css +0 -1
  75. package/dist/assets/mobile-BwduHUEq.js +0 -17
  76. package/dist/assets/stateDiagram-v2-BHNVJYJU-eVyb8_R4.js +0 -1
package/bin/compose.js CHANGED
@@ -119,6 +119,9 @@ if (!cmd || cmd === '--help' || cmd === '-h') {
119
119
  console.log(' roadmap generate Regenerate ROADMAP.md from feature.json files')
120
120
  console.log(' roadmap migrate Extract ROADMAP.md entries into feature.json files')
121
121
  console.log(' roadmap check Verify feature.json and ROADMAP.md are in sync')
122
+ console.log(' roadmap xref-sync Pull-reconcile feature.json external links to live state')
123
+ console.log(' items List vision items from local state (no server)')
124
+ console.log(' items show <id> Show detail for a specific vision item')
122
125
  console.log(' triage Analyze a feature and recommend build profile')
123
126
  console.log(' qa-scope Show affected routes from a feature\'s changed files')
124
127
  console.log(' init Initialize Compose in the current project')
@@ -1034,22 +1037,47 @@ _This is a seed design doc created by \`compose feature\`. The \`compose build\`
1034
1037
  if (cmd === 'roadmap') {
1035
1038
  const subcmd = args[0]
1036
1039
 
1037
- // compose roadmap generate — regenerate ROADMAP.md from feature.json files
1040
+ // compose roadmap generate — regenerate ROADMAP.md from feature.json files,
1041
+ // converging to a fixed point before finishing.
1038
1042
  if (subcmd === 'generate' || subcmd === 'gen') {
1039
1043
  const { writeRoadmap } = await import('../lib/roadmap-gen.js')
1044
+ const { checkRoundtrip } = await import('../lib/roadmap-roundtrip.js')
1045
+ const { listFeatures } = await import('../lib/feature-json.js')
1046
+ const { loadExternalPrefixes } = await import('../lib/project-paths.js')
1047
+ const { isNarrativeOwned } = await import('../lib/roadmap-config.js')
1040
1048
  const { root: cwd } = resolveCwdWithWorkspace(args)
1049
+ // Narrative-owned workspaces (#39): ROADMAP.md is hand-authored, not a render
1050
+ // of feature.json. writeRoadmap already no-ops, but the canonicalization pass
1051
+ // below would still overwrite the file (or crash if it's absent) — skip the
1052
+ // whole generate path here so the hand-authored file is never touched.
1053
+ if (isNarrativeOwned(cwd)) {
1054
+ console.log('narrative-owned workspace (roadmap.narrative=true) — ROADMAP.md is hand-authored; generate skipped.')
1055
+ process.exit(0)
1056
+ }
1041
1057
  const path = writeRoadmap(cwd)
1042
- console.log(`Generated ${path} from feature.json files`)
1058
+ const externalPrefixes = loadExternalPrefixes(cwd)
1059
+ // checkRoundtrip's now:'0000-00-00' is only used to detect/canonicalize
1060
+ // structural non-convergence — once the file has headings, readPreamble
1061
+ // preserves the existing preamble date verbatim, so no sentinel date leaks.
1062
+ const rt = checkRoundtrip(readFileSync(path, 'utf-8'), listFeatures(cwd), { now: '0000-00-00', externalPrefixes })
1063
+ if (!rt.fixedPoint) {
1064
+ writeFileSync(path, rt.canonical)
1065
+ console.log(`Generated ${path} (canonicalized over ${rt.passes} passes)`)
1066
+ } else {
1067
+ console.log(`Generated ${path} from feature.json files`)
1068
+ }
1043
1069
  process.exit(0)
1044
1070
  }
1045
1071
 
1046
1072
  // compose roadmap migrate — extract ROADMAP.md entries into feature.json files
1047
1073
  if (subcmd === 'migrate') {
1048
1074
  const { migrateRoadmap } = await import('../lib/migrate-roadmap.js')
1075
+ const { loadExternalPrefixes } = await import('../lib/project-paths.js')
1049
1076
  const { root: cwd } = resolveCwdWithWorkspace(args)
1050
1077
  const dryRun = args.includes('--dry-run')
1051
1078
  const overwrite = args.includes('--overwrite')
1052
- const result = migrateRoadmap(cwd, { dryRun, overwrite })
1079
+ const externalPrefixes = loadExternalPrefixes(cwd)
1080
+ const result = migrateRoadmap(cwd, { dryRun, overwrite, externalPrefixes })
1053
1081
  if (!dryRun) {
1054
1082
  console.log(`Created: ${result.created.length} feature.json files`)
1055
1083
  if (result.created.length > 0) console.log(` ${result.created.join(', ')}`)
@@ -1057,6 +1085,9 @@ if (cmd === 'roadmap') {
1057
1085
  if (result.updated.length > 0) console.log(` ${result.updated.join(', ')}`)
1058
1086
  console.log(`Skipped: ${result.skipped.length} (already exist, use --overwrite to replace)`)
1059
1087
  if (result.skipped.length > 0) console.log(` ${result.skipped.join(', ')}`)
1088
+ const ext = result.skippedExternal ?? []
1089
+ console.log(`Skipped (external, cross-project refs): ${ext.length}`)
1090
+ if (ext.length > 0) console.log(` ${ext.join(', ')}`)
1060
1091
  }
1061
1092
  process.exit(0)
1062
1093
  }
@@ -1064,49 +1095,56 @@ if (cmd === 'roadmap') {
1064
1095
  // compose roadmap check — verify feature.json ↔ ROADMAP.md consistency
1065
1096
  if (subcmd === 'check') {
1066
1097
  const { listFeatures } = await import('../lib/feature-json.js')
1067
- const { parseRoadmap } = await import('../lib/roadmap-parser.js')
1098
+ const { checkRoundtrip, describeLossyDiff } = await import('../lib/roadmap-roundtrip.js')
1099
+ const { loadExternalPrefixes } = await import('../lib/project-paths.js')
1068
1100
  const { root: cwd } = resolveCwdWithWorkspace(args)
1069
1101
  const roadmapPath = join(cwd, 'ROADMAP.md')
1070
1102
  if (!existsSync(roadmapPath)) {
1071
1103
  console.error('No ROADMAP.md found. Run: compose roadmap generate')
1072
1104
  process.exit(1)
1073
1105
  }
1074
- const features = listFeatures(cwd)
1075
- const roadmapEntries = parseRoadmap(readFileSync(roadmapPath, 'utf-8'))
1076
- const roadmapCodes = new Set(roadmapEntries.filter(e => !e.code.startsWith('_anon_')).map(e => e.code))
1077
- const featureCodes = new Set(features.map(f => f.code))
1078
-
1079
- let clean = true
1080
- // Features in feature.json but missing from ROADMAP.md
1081
- for (const f of features) {
1082
- if (!roadmapCodes.has(f.code)) {
1083
- console.log(`MISSING from ROADMAP.md: ${f.code}`)
1084
- clean = false
1085
- }
1106
+ // Narrative-owned workspaces (#39): ROADMAP.md is hand-authored, not a render
1107
+ // of feature.json the roundtrip would always report false drift. The file
1108
+ // must still EXIST (checked above); we only skip the drift comparison.
1109
+ const { isNarrativeOwned } = await import('../lib/roadmap-config.js')
1110
+ if (isNarrativeOwned(cwd)) {
1111
+ console.log('narrative-owned workspace (roadmap.narrative=true) — ROADMAP.md is hand-authored; roundtrip check skipped.')
1112
+ process.exit(0)
1086
1113
  }
1087
- // Features in ROADMAP.md but missing feature.json
1088
- for (const e of roadmapEntries) {
1089
- if (e.code.startsWith('_anon_')) continue
1090
- if (!featureCodes.has(e.code)) {
1091
- console.log(`NO feature.json: ${e.code}`)
1092
- clean = false
1093
- }
1114
+ const externalPrefixes = loadExternalPrefixes(cwd)
1115
+ const rt = checkRoundtrip(readFileSync(roadmapPath, 'utf-8'), listFeatures(cwd), { now: '0000-00-00', externalPrefixes })
1116
+ if (rt.fixedPoint && rt.lossless) {
1117
+ console.log('feature.json and ROADMAP.md are in sync (fixed point, lossless).')
1118
+ process.exit(0)
1094
1119
  }
1095
- // Status mismatches
1096
- const roadmapMap = new Map(roadmapEntries.map(e => [e.code, e]))
1097
- for (const f of features) {
1098
- const rm = roadmapMap.get(f.code)
1099
- if (rm && rm.status !== f.status) {
1100
- console.log(`STATUS MISMATCH: ${f.code} — feature.json=${f.status}, ROADMAP.md=${rm.status}`)
1101
- clean = false
1102
- }
1120
+ if (!rt.fixedPoint) {
1121
+ const d = rt.diffs.find(x => x.kind === 'FIXED_POINT_DIVERGENCE')
1122
+ console.log(`NOT A FIXED POINT: ${d?.detail ?? 'ROADMAP.md changes on regen'}`)
1103
1123
  }
1124
+ for (const d of rt.diffs.filter(x => x.kind.startsWith('LOSSLESS_'))) {
1125
+ console.log(describeLossyDiff(d))
1126
+ }
1127
+ console.log('\nRun `compose roadmap generate` to regenerate ROADMAP.md from feature.json.')
1128
+ process.exit(1)
1129
+ }
1104
1130
 
1105
- if (clean) {
1106
- console.log('feature.json and ROADMAP.md are in sync.')
1131
+ // compose roadmap xref-sync — pull-reconcile feature.json external links'
1132
+ // expect= to live target state (COMP-ROADMAP-XREF-SYNC v1). Never writes external.
1133
+ if (subcmd === 'xref-sync') {
1134
+ const { syncExternalRefs } = await import('../lib/xref-sync.js')
1135
+ const { root: cwd } = resolveCwdWithWorkspace(args)
1136
+ const dryRun = args.includes('--dry-run')
1137
+ const res = await syncExternalRefs(cwd, { dryRun })
1138
+ const verb = dryRun ? 'would update' : 'updated'
1139
+ if (res.synced.length === 0) {
1140
+ console.log(`No external-link drift to reconcile (${res.scanned} resolvable link(s) checked, ${res.unchanged} already in sync).`)
1107
1141
  } else {
1108
- console.log('\nRun `compose roadmap generate` to regenerate ROADMAP.md from feature.json.')
1109
- process.exit(1)
1142
+ console.log(`${dryRun ? 'Would reconcile' : 'Reconciled'} ${res.synced.length} external link(s):`)
1143
+ for (const s of res.synced) console.log(` ${s.code} ${s.provider} ${s.target}: expect ${s.from} → ${s.to} (${verb})`)
1144
+ }
1145
+ if (res.skipped.length > 0) {
1146
+ console.log(`\nSkipped ${res.skipped.length} unresolved link(s):`)
1147
+ for (const s of res.skipped) console.log(` ${s.code} ${s.provider} ${s.target}: ${s.reason}`)
1110
1148
  }
1111
1149
  process.exit(0)
1112
1150
  }
@@ -2692,6 +2730,112 @@ if (cmd === 'build') {
2692
2730
  process.exit(1)
2693
2731
  }
2694
2732
 
2733
+ } else if (cmd === 'items') {
2734
+ // ---------------------------------------------------------------------------
2735
+ // compose items — read vision-state.json directly (no server required)
2736
+ // ---------------------------------------------------------------------------
2737
+ const itemsSub = args[0] && !args[0].startsWith('-') ? args[0] : 'list'
2738
+ const jsonFlag = args.includes('--json')
2739
+
2740
+ const { root: itemsCwd } = resolveCwdWithWorkspace(args)
2741
+ const vsPath = join(itemsCwd, '.compose', 'data', 'vision-state.json')
2742
+
2743
+ if (!existsSync(vsPath)) {
2744
+ console.error('No vision state found at .compose/data/vision-state.json')
2745
+ console.error('Run `compose start` at least once to generate it, then you can use `compose items` offline.')
2746
+ process.exit(1)
2747
+ }
2748
+
2749
+ let visionState
2750
+ try {
2751
+ visionState = JSON.parse(readFileSync(vsPath, 'utf-8'))
2752
+ } catch (err) {
2753
+ console.error(`Failed to read vision state: ${err.message}`)
2754
+ process.exit(1)
2755
+ }
2756
+
2757
+ const allItems = visionState.items || []
2758
+
2759
+ if (itemsSub === 'list') {
2760
+ if (jsonFlag) {
2761
+ console.log(JSON.stringify(allItems, null, 2))
2762
+ process.exit(0)
2763
+ }
2764
+
2765
+ if (allItems.length === 0) {
2766
+ console.log('No items found.')
2767
+ process.exit(0)
2768
+ }
2769
+
2770
+ // Table output: id (truncated), title, status, type
2771
+ const header = { id: 'ID', title: 'TITLE', status: 'STATUS', type: 'TYPE' }
2772
+ const rows = allItems.map(it => ({
2773
+ id: (it.id || '').slice(0, 8),
2774
+ title: (it.title || '(untitled)').slice(0, 50),
2775
+ status: it.status || '-',
2776
+ type: it.type || '-',
2777
+ }))
2778
+
2779
+ // Compute column widths
2780
+ const cols = ['id', 'title', 'status', 'type']
2781
+ const widths = {}
2782
+ for (const c of cols) {
2783
+ widths[c] = Math.max(header[c].length, ...rows.map(r => r[c].length))
2784
+ }
2785
+
2786
+ const pad = (s, w) => s + ' '.repeat(Math.max(0, w - s.length))
2787
+ const line = cols.map(c => pad(header[c], widths[c])).join(' ')
2788
+ console.log(line)
2789
+ console.log(cols.map(c => '-'.repeat(widths[c])).join(' '))
2790
+ for (const r of rows) {
2791
+ console.log(cols.map(c => pad(r[c], widths[c])).join(' '))
2792
+ }
2793
+ process.exit(0)
2794
+
2795
+ } else if (itemsSub === 'show') {
2796
+ const positionalArgs = args.filter(a => !a.startsWith('-'))
2797
+ const showId = positionalArgs[1]
2798
+ if (!showId) {
2799
+ console.error('Usage: compose items show <id>')
2800
+ process.exit(1)
2801
+ }
2802
+
2803
+ const matches = allItems.filter(it => it.id === showId || it.id.startsWith(showId))
2804
+ if (matches.length > 1) {
2805
+ console.error(`Ambiguous ID prefix '${showId}' matches ${matches.length} items:`)
2806
+ for (const m of matches) console.error(` ${(m.id || '').slice(0, 8)} ${m.title || '(untitled)'}`)
2807
+ process.exit(1)
2808
+ }
2809
+ const match = matches[0]
2810
+ if (!match) {
2811
+ console.error(`No item found matching: ${showId}`)
2812
+ process.exit(1)
2813
+ }
2814
+
2815
+ if (jsonFlag) {
2816
+ console.log(JSON.stringify(match, null, 2))
2817
+ } else {
2818
+ for (const [key, val] of Object.entries(match)) {
2819
+ if (val == null) continue
2820
+ if (typeof val === 'object') {
2821
+ console.log(`${key}: ${JSON.stringify(val)}`)
2822
+ } else {
2823
+ console.log(`${key}: ${val}`)
2824
+ }
2825
+ }
2826
+ }
2827
+ process.exit(0)
2828
+
2829
+ } else {
2830
+ console.error(`Unknown items subcommand: ${itemsSub}`)
2831
+ console.error('Usage:')
2832
+ console.error(' compose items List all items')
2833
+ console.error(' compose items list List all items')
2834
+ console.error(' compose items show <id> Show detail for a specific item')
2835
+ console.error(' --json Output as JSON')
2836
+ process.exit(1)
2837
+ }
2838
+
2695
2839
  } else {
2696
2840
  console.error(`Unknown command: ${cmd}`)
2697
2841
  process.exit(1)