orbital-command 0.3.0 → 1.0.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 (160) hide show
  1. package/README.md +67 -42
  2. package/bin/commands/config.js +19 -0
  3. package/bin/commands/events.js +40 -0
  4. package/bin/commands/launch.js +126 -0
  5. package/bin/commands/manifest.js +283 -0
  6. package/bin/commands/registry.js +104 -0
  7. package/bin/commands/update.js +24 -0
  8. package/bin/lib/helpers.js +229 -0
  9. package/bin/orbital.js +95 -870
  10. package/dist/assets/Landing-CfQdHR0N.js +11 -0
  11. package/dist/assets/PrimitivesConfig-DThSipFy.js +32 -0
  12. package/dist/assets/QualityGates-B4kxM5UU.js +26 -0
  13. package/dist/assets/SessionTimeline-Bz1iZnmg.js +1 -0
  14. package/dist/assets/Settings-DLcZwbCT.js +12 -0
  15. package/dist/assets/SourceControl-BMNIz7Lt.js +36 -0
  16. package/dist/assets/WorkflowVisualizer-CxuSBOYu.js +69 -0
  17. package/dist/assets/{arrow-down-CPy85_J6.js → arrow-down-DVPp6_qp.js} +1 -1
  18. package/dist/assets/bot-NFaJBDn_.js +6 -0
  19. package/dist/assets/{charts-DbDg0Psc.js → charts-LGLb8hyU.js} +1 -1
  20. package/dist/assets/{circle-x-Cwz6ZQDV.js → circle-x-IsFCkBZu.js} +1 -1
  21. package/dist/assets/{file-text-C46Xr65c.js → file-text-J1cebZXF.js} +1 -1
  22. package/dist/assets/{globe-Cn2yNZUD.js → globe-WzeyHsUc.js} +1 -1
  23. package/dist/assets/index-BdJ57EhC.css +1 -0
  24. package/dist/assets/index-o4ScMAuR.js +349 -0
  25. package/dist/assets/{key-OPaNTWJ5.js → key-CKR8JJSj.js} +1 -1
  26. package/dist/assets/{minus-GMsbpKym.js → minus-CHBsJyjp.js} +1 -1
  27. package/dist/assets/radio-xqZaR-Uk.js +6 -0
  28. package/dist/assets/rocket-D_xvvNG6.js +6 -0
  29. package/dist/assets/{shield-DwAFkDYI.js → shield-TdB1yv_a.js} +1 -1
  30. package/dist/assets/useSocketListener-0L5yiN5i.js +1 -0
  31. package/dist/assets/useWorkflowEditor-CqeRWVQX.js +11 -0
  32. package/dist/assets/workflow-constants-Rw-GmgHZ.js +6 -0
  33. package/dist/assets/zap-C9wqYMpl.js +6 -0
  34. package/dist/index.html +3 -3
  35. package/dist/server/server/__tests__/data-routes.test.js +2 -0
  36. package/dist/server/server/__tests__/scope-routes.test.js +1 -0
  37. package/dist/server/server/config-migrator.js +0 -3
  38. package/dist/server/server/config.js +35 -6
  39. package/dist/server/server/database.js +0 -22
  40. package/dist/server/server/index.js +26 -814
  41. package/dist/server/server/init.js +32 -399
  42. package/dist/server/server/launch.js +1 -1
  43. package/dist/server/server/parsers/event-parser.js +4 -1
  44. package/dist/server/server/project-context.js +19 -9
  45. package/dist/server/server/project-manager.js +6 -6
  46. package/dist/server/server/routes/aggregate-routes.js +871 -0
  47. package/dist/server/server/routes/config-routes.js +41 -88
  48. package/dist/server/server/routes/data-routes.js +5 -15
  49. package/dist/server/server/routes/dispatch-routes.js +24 -8
  50. package/dist/server/server/routes/manifest-routes.js +1 -1
  51. package/dist/server/server/routes/scope-routes.js +10 -7
  52. package/dist/server/server/schema.js +1 -0
  53. package/dist/server/server/services/batch-orchestrator.js +17 -3
  54. package/dist/server/server/services/config-service.js +10 -1
  55. package/dist/server/server/services/scope-service.js +7 -7
  56. package/dist/server/server/services/sprint-orchestrator.js +24 -11
  57. package/dist/server/server/services/sprint-service.js +2 -2
  58. package/dist/server/server/uninstall.js +195 -0
  59. package/dist/server/server/update.js +212 -0
  60. package/dist/server/server/utils/dispatch-utils.js +8 -6
  61. package/dist/server/server/utils/flag-builder.js +54 -0
  62. package/dist/server/server/utils/json-fields.js +14 -0
  63. package/dist/server/server/utils/json-fields.test.js +73 -0
  64. package/dist/server/server/utils/route-helpers.js +37 -0
  65. package/dist/server/server/utils/route-helpers.test.js +115 -0
  66. package/dist/server/server/watchers/event-watcher.js +28 -13
  67. package/dist/server/server/wizard/config-editor.js +4 -4
  68. package/dist/server/server/wizard/doctor.js +2 -2
  69. package/dist/server/server/wizard/index.js +224 -39
  70. package/dist/server/server/wizard/phases/welcome.js +1 -4
  71. package/dist/server/server/wizard/ui.js +6 -7
  72. package/dist/server/shared/api-types.js +80 -1
  73. package/dist/server/shared/workflow-engine.js +1 -1
  74. package/package.json +20 -20
  75. package/schemas/orbital.config.schema.json +1 -19
  76. package/scripts/postinstall.js +6 -42
  77. package/scripts/release.sh +53 -0
  78. package/server/__tests__/data-routes.test.ts +2 -0
  79. package/server/__tests__/scope-routes.test.ts +1 -0
  80. package/server/config-migrator.ts +0 -3
  81. package/server/config.ts +39 -11
  82. package/server/database.ts +0 -26
  83. package/server/global-config.ts +4 -0
  84. package/server/index.ts +29 -894
  85. package/server/init.ts +32 -443
  86. package/server/launch.ts +1 -1
  87. package/server/parsers/event-parser.ts +4 -1
  88. package/server/project-context.ts +26 -10
  89. package/server/project-manager.ts +5 -6
  90. package/server/routes/aggregate-routes.ts +968 -0
  91. package/server/routes/config-routes.ts +41 -81
  92. package/server/routes/data-routes.ts +7 -16
  93. package/server/routes/dispatch-routes.ts +29 -8
  94. package/server/routes/manifest-routes.ts +1 -1
  95. package/server/routes/scope-routes.ts +12 -7
  96. package/server/schema.ts +1 -0
  97. package/server/services/batch-orchestrator.ts +18 -2
  98. package/server/services/config-service.ts +10 -1
  99. package/server/services/scope-service.ts +6 -6
  100. package/server/services/sprint-orchestrator.ts +24 -9
  101. package/server/services/sprint-service.ts +2 -2
  102. package/server/uninstall.ts +214 -0
  103. package/server/update.ts +263 -0
  104. package/server/utils/dispatch-utils.ts +8 -6
  105. package/server/utils/flag-builder.ts +56 -0
  106. package/server/utils/json-fields.test.ts +83 -0
  107. package/server/utils/json-fields.ts +14 -0
  108. package/server/utils/route-helpers.test.ts +144 -0
  109. package/server/utils/route-helpers.ts +38 -0
  110. package/server/watchers/event-watcher.ts +24 -12
  111. package/server/wizard/config-editor.ts +4 -4
  112. package/server/wizard/doctor.ts +2 -2
  113. package/server/wizard/index.ts +291 -40
  114. package/server/wizard/phases/welcome.ts +1 -5
  115. package/server/wizard/ui.ts +6 -7
  116. package/shared/api-types.ts +106 -0
  117. package/shared/workflow-engine.ts +1 -1
  118. package/templates/agents/QUICK-REFERENCE.md +1 -0
  119. package/templates/agents/README.md +1 -0
  120. package/templates/agents/SKILL-TRIGGERS.md +11 -0
  121. package/templates/agents/green-team/deep-dive.md +361 -0
  122. package/templates/hooks/end-session.sh +1 -0
  123. package/templates/hooks/init-session.sh +1 -0
  124. package/templates/hooks/scope-commit-logger.sh +2 -2
  125. package/templates/hooks/scope-create-gate.sh +2 -4
  126. package/templates/hooks/scope-gate.sh +4 -6
  127. package/templates/hooks/scope-helpers.sh +10 -1
  128. package/templates/hooks/scope-lifecycle-gate.sh +14 -5
  129. package/templates/hooks/scope-prepare.sh +1 -1
  130. package/templates/hooks/scope-transition.sh +14 -6
  131. package/templates/hooks/time-tracker.sh +2 -5
  132. package/templates/orbital.config.json +1 -4
  133. package/templates/presets/development.json +4 -4
  134. package/templates/presets/gitflow.json +7 -0
  135. package/templates/prompts/README.md +23 -0
  136. package/templates/prompts/deep-dive-audit.md +94 -0
  137. package/templates/quick/rules.md +56 -5
  138. package/templates/skills/git-commit/SKILL.md +21 -6
  139. package/templates/skills/git-dev/SKILL.md +8 -4
  140. package/templates/skills/git-main/SKILL.md +8 -4
  141. package/templates/skills/git-production/SKILL.md +6 -3
  142. package/templates/skills/git-staging/SKILL.md +6 -3
  143. package/templates/skills/scope-fix-review/SKILL.md +8 -4
  144. package/templates/skills/scope-implement/SKILL.md +13 -5
  145. package/templates/skills/scope-post-review/SKILL.md +16 -4
  146. package/templates/skills/scope-pre-review/SKILL.md +6 -2
  147. package/dist/assets/PrimitivesConfig-CrmQXYh4.js +0 -32
  148. package/dist/assets/QualityGates-BbasOsF3.js +0 -21
  149. package/dist/assets/SessionTimeline-CGeJsVvy.js +0 -1
  150. package/dist/assets/Settings-oiM496mc.js +0 -12
  151. package/dist/assets/SourceControl-B1fP2nJL.js +0 -41
  152. package/dist/assets/WorkflowVisualizer-CWLYf-f0.js +0 -74
  153. package/dist/assets/formatDistanceToNow-BMqsSP44.js +0 -1
  154. package/dist/assets/index-Aj4sV8Al.css +0 -1
  155. package/dist/assets/index-Bc9dK3MW.js +0 -354
  156. package/dist/assets/useWorkflowEditor-BJkTX_NR.js +0 -16
  157. package/dist/assets/zap-DfbUoOty.js +0 -11
  158. package/dist/server/server/services/telemetry-service.js +0 -143
  159. package/server/services/telemetry-service.ts +0 -195
  160. /package/{shared/default-workflow.json → templates/presets/default.json} +0 -0
@@ -1,17 +1,13 @@
1
1
  /**
2
- * Shared init logic — used by both the CLI (`orbital init`) and
3
- * programmatic callers (e.g. tests).
2
+ * Shared init logic — used by the wizard and
3
+ * programmatic callers (e.g. tests, API routes).
4
4
  */
5
5
  import fs from 'fs';
6
6
  import path from 'path';
7
7
  import { fileURLToPath } from 'url';
8
- import { GLOBAL_PRIMITIVES_DIR, ensureOrbitalHome, unregisterProject } from './global-config.js';
9
- import { loadManifest, saveManifest, createManifest, hashFile, buildTemplateInventory, templateFileRecord, userFileRecord, isSelfHosting, getSymlinkTarget, createBackup, refreshFileStatuses, reverseRemapPath, safeBackupFile, safeCopyTemplate, } from './manifest.js';
10
- import { needsLegacyMigration, migrateFromLegacy } from './migrate-legacy.js';
11
- import { computeUpdatePlan, loadRenameMap, formatPlan, getFilesToBackup } from './update-planner.js';
12
- import { syncSettingsHooks, removeAllOrbitalHooks, getTemplateChecksum } from './settings-sync.js';
13
- import { migrateConfig } from './config-migrator.js';
14
- import { validate, formatValidationReport } from './validator.js';
8
+ import { GLOBAL_PRIMITIVES_DIR, ensureOrbitalHome } from './global-config.js';
9
+ import { saveManifest, createManifest, hashFile, buildTemplateInventory, templateFileRecord, userFileRecord, isSelfHosting, getSymlinkTarget, } from './manifest.js';
10
+ import { getTemplateChecksum } from './settings-sync.js';
15
11
  import { getPackageVersion as _getPackageVersionUtil } from './utils/package-info.js';
16
12
  const __filename = fileURLToPath(import.meta.url);
17
13
  const __dirname = path.dirname(__filename);
@@ -190,7 +186,8 @@ function generateManifest(config) {
190
186
  lines.push(`WORKFLOW_TERMINAL_STATUSES="${terminalStatuses.join(' ')}"`);
191
187
  lines.push('');
192
188
  lines.push('# ─── Entry point status ───');
193
- lines.push(`WORKFLOW_ENTRY_STATUS="${config.entryPoint || lists[0]?.id || 'todo'}"`);
189
+ const entryPointId = lists.find((l) => l.isEntryPoint)?.id || lists[0]?.id || 'todo';
190
+ lines.push(`WORKFLOW_ENTRY_STATUS="${entryPointId}"`);
194
191
  lines.push('');
195
192
  const listMap = new Map(lists.map((l) => [l.id, l]));
196
193
  lines.push('# ─── Transition edges (from:to:sessionKey) ───');
@@ -213,6 +210,25 @@ function generateManifest(config) {
213
210
  }
214
211
  lines.push(')');
215
212
  lines.push('');
213
+ lines.push('# ─── Commit session branch patterns (regex) ───');
214
+ lines.push(`WORKFLOW_COMMIT_BRANCHES="${config.commitBranchPatterns ?? ''}"`);
215
+ lines.push('');
216
+ lines.push('# ─── Backward-compat direction aliases (alias:from:to:sessionKey) ───');
217
+ lines.push('WORKFLOW_DIRECTION_ALIASES=(');
218
+ for (const edge of config.edges || []) {
219
+ if (edge.direction !== 'forward' || !edge.dispatchOnly)
220
+ continue;
221
+ const targetList = listMap.get(edge.to);
222
+ if (!targetList)
223
+ continue;
224
+ const group = targetList.group;
225
+ if (group?.startsWith('deploy')) {
226
+ const sessionKey = targetList.sessionKey ?? '';
227
+ lines.push(` "to-${edge.to}:${edge.from}:${edge.to}:${sessionKey}"`);
228
+ }
229
+ }
230
+ lines.push(')');
231
+ lines.push('');
216
232
  lines.push('# ─── Helper functions ──────────────────────────────');
217
233
  lines.push('');
218
234
  lines.push('status_to_dir() {');
@@ -534,10 +550,13 @@ export function seedGlobalPrimitives() {
534
550
  }
535
551
  }
536
552
  }
537
- export { TEMPLATES_DIR, ensureDir };
553
+ export { TEMPLATES_DIR, ensureDir, cleanEmptyDirs, chmodScripts, listTemplateFiles, writeManifest, generateIndexMd, getPackageVersion };
538
554
  // Re-export manifest utilities for CLI access via loadSharedModule()
539
555
  export { loadManifest, saveManifest, hashFile, buildTemplateInventory, refreshFileStatuses, summarizeManifest } from './manifest.js';
540
556
  export { validate, formatValidationReport } from './validator.js';
557
+ // Re-export update and uninstall from their new homes (backward compat for loadSharedModule)
558
+ export { runUpdate } from './update.js';
559
+ export { runUninstall } from './uninstall.js';
541
560
  export function runInit(projectRoot, options = {}) {
542
561
  const force = options.force ?? false;
543
562
  const quiet = options.quiet ?? false;
@@ -806,391 +825,5 @@ export function runInit(projectRoot, options = {}) {
806
825
  const totalSkipped = hooksResult.skipped.length + skillsResult.skipped.length + agentsResult.skipped.length;
807
826
  log(`\nDone. ${totalCreated} files installed, ${totalSkipped} skipped (use --force to overwrite).`);
808
827
  }
809
- export function runUpdate(projectRoot, options = {}) {
810
- const { dryRun = false } = options;
811
- const claudeDir = path.join(projectRoot, '.claude');
812
- const newVersion = getPackageVersion();
813
- console.log(`\nOrbital Command — update${dryRun ? ' (dry run)' : ''}`);
814
- console.log(`Project root: ${projectRoot}\n`);
815
- // 1. Load or create manifest (auto-migrate legacy installs)
816
- let manifest = loadManifest(projectRoot);
817
- if (!manifest) {
818
- if (needsLegacyMigration(projectRoot)) {
819
- console.log(' Migrating from legacy install...');
820
- const result = migrateFromLegacy(projectRoot, TEMPLATES_DIR, newVersion);
821
- console.log(` Migrated ${result.synced} synced, ${result.modified} modified, ${result.userOwned} user-owned files`);
822
- if (result.importedPins > 0)
823
- console.log(` Imported ${result.importedPins} pinned files from orbital-sync.json`);
824
- manifest = loadManifest(projectRoot);
825
- }
826
- if (!manifest) {
827
- console.log(' No manifest found. Run `orbital init` first.');
828
- return;
829
- }
830
- }
831
- const oldVersion = manifest.packageVersion;
832
- // 1b. Refresh file statuses so outdated vs modified is accurate
833
- refreshFileStatuses(manifest, claudeDir);
834
- // 2. Compute update plan
835
- const renameMap = loadRenameMap(TEMPLATES_DIR, oldVersion, newVersion);
836
- const plan = computeUpdatePlan({
837
- templatesDir: TEMPLATES_DIR,
838
- claudeDir,
839
- manifest,
840
- newVersion,
841
- renameMap,
842
- });
843
- // 3. Dry-run mode — print plan and exit
844
- if (dryRun) {
845
- console.log(formatPlan(plan, oldVersion, newVersion));
846
- return;
847
- }
848
- if (plan.isEmpty && oldVersion === newVersion) {
849
- console.log(' Everything up to date. No changes needed.');
850
- }
851
- // 4. Create backup of files that will be modified
852
- const filesToBackup = getFilesToBackup(plan);
853
- if (filesToBackup.length > 0) {
854
- const backupDir = createBackup(claudeDir, filesToBackup);
855
- if (backupDir) {
856
- console.log(` Backup ${filesToBackup.length} files → ${path.relative(claudeDir, backupDir)}/`);
857
- }
858
- }
859
- // 5. Execute plan
860
- const templateInventory = buildTemplateInventory(TEMPLATES_DIR);
861
- // 5a. Handle renames
862
- for (const { from, to } of plan.toRename) {
863
- const fromPath = path.join(claudeDir, from);
864
- const toPath = path.join(claudeDir, to);
865
- const toDir = path.dirname(toPath);
866
- if (!fs.existsSync(toDir))
867
- fs.mkdirSync(toDir, { recursive: true });
868
- if (fs.existsSync(fromPath)) {
869
- safeBackupFile(fromPath);
870
- const stat = fs.lstatSync(fromPath);
871
- if (stat.isSymbolicLink()) {
872
- // Recreate symlink at new path pointing to remapped template
873
- const target = fs.readlinkSync(fromPath);
874
- fs.unlinkSync(fromPath);
875
- fs.symlinkSync(target, toPath);
876
- }
877
- else {
878
- fs.renameSync(fromPath, toPath);
879
- }
880
- }
881
- // Transfer manifest record
882
- const record = manifest.files[from];
883
- if (record) {
884
- delete manifest.files[from];
885
- const newHash = templateInventory.get(to);
886
- manifest.files[to] = { ...record, templateHash: newHash, installedHash: newHash || record.installedHash };
887
- }
888
- console.log(` RENAME ${from} → ${to}`);
889
- }
890
- // 5b. Add new files
891
- for (const filePath of plan.toAdd) {
892
- const templateHash = templateInventory.get(filePath);
893
- if (!templateHash)
894
- continue;
895
- const destPath = path.join(claudeDir, filePath);
896
- const destDir = path.dirname(destPath);
897
- if (!fs.existsSync(destDir))
898
- fs.mkdirSync(destDir, { recursive: true });
899
- copyTemplateFile(TEMPLATES_DIR, filePath, destPath);
900
- manifest.files[filePath] = templateFileRecord(templateHash);
901
- console.log(` ADD ${filePath}`);
902
- }
903
- // 5c. Update changed synced/outdated files
904
- for (const filePath of plan.toUpdate) {
905
- const templateHash = templateInventory.get(filePath);
906
- if (!templateHash)
907
- continue;
908
- const destPath = path.join(claudeDir, filePath);
909
- safeBackupFile(destPath);
910
- copyTemplateFile(TEMPLATES_DIR, filePath, destPath);
911
- manifest.files[filePath] = templateFileRecord(templateHash);
912
- console.log(` UPDATE ${filePath}`);
913
- }
914
- // 5d. Remove deleted files
915
- for (const filePath of plan.toRemove) {
916
- const absPath = path.join(claudeDir, filePath);
917
- if (fs.existsSync(absPath)) {
918
- safeBackupFile(absPath);
919
- fs.unlinkSync(absPath);
920
- }
921
- delete manifest.files[filePath];
922
- console.log(` REMOVE ${filePath}`);
923
- }
924
- // 5e. Update pinned/modified file records (record new template hash without touching file)
925
- for (const { file, reason, newTemplateHash } of plan.toSkip) {
926
- if (manifest.files[file]) {
927
- manifest.files[file].templateHash = newTemplateHash;
928
- }
929
- if (reason === 'modified') {
930
- console.log(` SKIP ${file} (user modified)`);
931
- }
932
- else {
933
- console.log(` SKIP ${file} (pinned)`);
934
- }
935
- }
936
- // 5f. Clean up empty directories
937
- for (const dir of ['hooks', 'skills', 'agents', 'config/workflows']) {
938
- const dirPath = path.join(claudeDir, dir);
939
- if (fs.existsSync(dirPath))
940
- cleanEmptyDirs(dirPath);
941
- }
942
- // 6. Bidirectional settings hook sync
943
- const settingsTarget = path.join(claudeDir, 'settings.local.json');
944
- const settingsSrc = path.join(TEMPLATES_DIR, 'settings-hooks.json');
945
- const syncResult = syncSettingsHooks(settingsTarget, settingsSrc, manifest.settingsHooksChecksum, renameMap);
946
- if (!syncResult.skipped) {
947
- console.log(` Settings +${syncResult.added} -${syncResult.removed} hooks (${syncResult.updated} renamed)`);
948
- manifest.settingsHooksChecksum = getTemplateChecksum(settingsSrc);
949
- }
950
- // 7. Config migrations
951
- const configPath = path.join(claudeDir, 'orbital.config.json');
952
- const migrationResult = migrateConfig(configPath, manifest.appliedMigrations);
953
- if (migrationResult.applied.length > 0) {
954
- manifest.appliedMigrations.push(...migrationResult.applied);
955
- console.log(` Config ${migrationResult.applied.length} migration(s) applied`);
956
- }
957
- if (migrationResult.defaultsFilled.length > 0) {
958
- console.log(` Config ${migrationResult.defaultsFilled.length} default(s) filled`);
959
- }
960
- // 8. Regenerate derived artifacts (always)
961
- const workflowManifestOk = writeManifest(claudeDir);
962
- console.log(` ${workflowManifestOk ? 'Updated' : 'Skipped'} .claude/config/workflow-manifest.sh`);
963
- const indexContent = generateIndexMd(projectRoot, claudeDir);
964
- fs.writeFileSync(path.join(claudeDir, 'INDEX.md'), indexContent, 'utf8');
965
- console.log(` Updated .claude/INDEX.md`);
966
- // 9. Update agent-triggers.json (template-managed)
967
- const triggersSrc = path.join(TEMPLATES_DIR, 'config', 'agent-triggers.json');
968
- const triggersDest = path.join(claudeDir, 'config', 'agent-triggers.json');
969
- if (fs.existsSync(triggersSrc)) {
970
- fs.copyFileSync(triggersSrc, triggersDest);
971
- console.log(` Updated .claude/config/agent-triggers.json`);
972
- }
973
- // 10. Update scope template
974
- const scopeTemplateSrc = path.join(TEMPLATES_DIR, 'scopes', '_template.md');
975
- const scopeTemplateDest = path.join(projectRoot, 'scopes', '_template.md');
976
- if (fs.existsSync(scopeTemplateSrc)) {
977
- ensureDir(path.join(projectRoot, 'scopes'));
978
- fs.copyFileSync(scopeTemplateSrc, scopeTemplateDest);
979
- }
980
- // 11. Make hook scripts executable
981
- chmodScripts(path.join(claudeDir, 'hooks'));
982
- // 12. Refresh global primitives
983
- seedGlobalPrimitives();
984
- // 13. Update manifest metadata
985
- manifest.previousPackageVersion = oldVersion;
986
- manifest.packageVersion = newVersion;
987
- manifest.updatedAt = new Date().toISOString();
988
- saveManifest(projectRoot, manifest);
989
- // 14. Validate
990
- const report = validate(projectRoot, newVersion);
991
- if (report.errors > 0) {
992
- console.log(`\n Validation: ${report.errors} errors found`);
993
- console.log(formatValidationReport(report));
994
- }
995
- const totalChanges = plan.toAdd.length + plan.toUpdate.length + plan.toRemove.length + plan.toRename.length;
996
- console.log(`\nUpdate complete. ${totalChanges} file changes, ${plan.toSkip.length} skipped.\n`);
997
- }
998
- export function runUninstall(projectRoot, options = {}) {
999
- const { dryRun = false, keepConfig = false } = options;
1000
- const claudeDir = path.join(projectRoot, '.claude');
1001
- console.log(`\nOrbital Command — uninstall${dryRun ? ' (dry run)' : ''}`);
1002
- console.log(`Project root: ${projectRoot}\n`);
1003
- const manifest = loadManifest(projectRoot);
1004
- // Fall back to legacy uninstall if no manifest
1005
- if (!manifest) {
1006
- console.log(' No manifest found — falling back to legacy uninstall.');
1007
- runLegacyUninstall(projectRoot);
1008
- return;
1009
- }
1010
- // Compute what to remove vs preserve
1011
- const toRemove = [];
1012
- const toPreserve = [];
1013
- for (const [filePath, record] of Object.entries(manifest.files)) {
1014
- if (record.origin === 'user') {
1015
- toPreserve.push(filePath);
1016
- }
1017
- else if (record.status === 'modified' || record.status === 'outdated') {
1018
- toPreserve.push(filePath);
1019
- }
1020
- else {
1021
- toRemove.push(filePath);
1022
- }
1023
- }
1024
- if (dryRun) {
1025
- console.log(' Files to REMOVE:');
1026
- for (const f of toRemove)
1027
- console.log(` ${f}`);
1028
- if (toPreserve.length > 0) {
1029
- console.log(' Files to PRESERVE:');
1030
- for (const f of toPreserve)
1031
- console.log(` ${f} (${manifest.files[f].origin}/${manifest.files[f].status})`);
1032
- }
1033
- console.log(`\n Would also remove: settings hooks, generated artifacts, config files, gitignore entries`);
1034
- console.log(` No changes made. Run without --dry-run to apply.`);
1035
- return;
1036
- }
1037
- // 1. Remove _orbital hooks from settings.local.json
1038
- const settingsPath = path.join(claudeDir, 'settings.local.json');
1039
- const removedHooks = removeAllOrbitalHooks(settingsPath);
1040
- console.log(` Removed ${removedHooks} orbital hook registrations`);
1041
- // 2. Delete template files (synced + pinned, not modified or user-owned)
1042
- let filesRemoved = 0;
1043
- for (const filePath of toRemove) {
1044
- const absPath = path.join(claudeDir, filePath);
1045
- if (fs.existsSync(absPath)) {
1046
- fs.unlinkSync(absPath);
1047
- filesRemoved++;
1048
- }
1049
- }
1050
- console.log(` Removed ${filesRemoved} template files`);
1051
- if (toPreserve.length > 0) {
1052
- console.log(` Preserved ${toPreserve.length} user/modified files`);
1053
- }
1054
- // 3. Clean up empty directories
1055
- for (const dir of ['hooks', 'skills', 'agents', 'config/workflows', 'quick', 'anti-patterns']) {
1056
- const dirPath = path.join(claudeDir, dir);
1057
- if (fs.existsSync(dirPath))
1058
- cleanEmptyDirs(dirPath);
1059
- }
1060
- // 4. Remove generated artifacts
1061
- for (const artifact of manifest.generatedArtifacts) {
1062
- const artifactPath = path.join(claudeDir, artifact);
1063
- if (fs.existsSync(artifactPath)) {
1064
- fs.unlinkSync(artifactPath);
1065
- console.log(` Removed .claude/${artifact}`);
1066
- }
1067
- }
1068
- // 5. Remove template-sourced config files
1069
- const configFiles = [
1070
- 'config/agent-triggers.json',
1071
- 'config/workflow.json',
1072
- 'lessons-learned.md',
1073
- ];
1074
- for (const file of configFiles) {
1075
- const filePath = path.join(claudeDir, file);
1076
- if (fs.existsSync(filePath)) {
1077
- fs.unlinkSync(filePath);
1078
- console.log(` Removed .claude/${file}`);
1079
- }
1080
- }
1081
- // Remove config/workflows/ directory entirely
1082
- const workflowsDir = path.join(claudeDir, 'config', 'workflows');
1083
- if (fs.existsSync(workflowsDir)) {
1084
- fs.rmSync(workflowsDir, { recursive: true, force: true });
1085
- console.log(` Removed .claude/config/workflows/`);
1086
- }
1087
- // 6. Remove gitignore entries
1088
- removeGitignoreEntries(projectRoot, manifest.gitignoreEntries);
1089
- console.log(` Cleaned .gitignore`);
1090
- // 7. Deregister from global registry
1091
- if (unregisterProject(projectRoot)) {
1092
- console.log(` Removed project from ~/.orbital/config.json`);
1093
- }
1094
- // 8. Remove orbital config and manifest (unless --keep-config)
1095
- if (!keepConfig) {
1096
- const toClean = ['orbital.config.json', 'orbital-manifest.json', 'orbital-sync.json'];
1097
- for (const file of toClean) {
1098
- const filePath = path.join(claudeDir, file);
1099
- if (fs.existsSync(filePath))
1100
- fs.unlinkSync(filePath);
1101
- }
1102
- // Remove backups directory
1103
- const backupsDir = path.join(claudeDir, '.orbital-backups');
1104
- if (fs.existsSync(backupsDir))
1105
- fs.rmSync(backupsDir, { recursive: true, force: true });
1106
- console.log(` Removed orbital config and manifest`);
1107
- }
1108
- else {
1109
- // Still remove the manifest — it's invalid after uninstall
1110
- const manifestPath = path.join(claudeDir, 'orbital-manifest.json');
1111
- if (fs.existsSync(manifestPath))
1112
- fs.unlinkSync(manifestPath);
1113
- console.log(` Kept orbital.config.json (--keep-config)`);
1114
- }
1115
- // Clean up remaining empty directories
1116
- for (const dir of ['config', 'quick', 'anti-patterns', 'review-verdicts']) {
1117
- const dirPath = path.join(claudeDir, dir);
1118
- if (fs.existsSync(dirPath))
1119
- cleanEmptyDirs(dirPath);
1120
- }
1121
- const total = removedHooks + filesRemoved;
1122
- console.log(`\nUninstall complete. ${total} items removed.`);
1123
- if (toPreserve.length > 0) {
1124
- console.log(`Note: ${toPreserve.length} user/modified files were preserved.`);
1125
- }
1126
- console.log(`Note: scopes/ and .claude/orbital-events/ were preserved.\n`);
1127
- }
1128
- /** Legacy uninstall for projects without a manifest (backward compat). */
1129
- function runLegacyUninstall(projectRoot) {
1130
- const claudeDir = path.join(projectRoot, '.claude');
1131
- // Remove orbital hooks from settings.local.json
1132
- const settingsPath = path.join(claudeDir, 'settings.local.json');
1133
- const removedHooks = removeAllOrbitalHooks(settingsPath);
1134
- console.log(` Removed ${removedHooks} orbital hook registrations`);
1135
- // Delete hooks/skills/agents that match template files
1136
- for (const dir of ['hooks', 'skills', 'agents']) {
1137
- const templateFiles = listTemplateFiles(path.join(TEMPLATES_DIR, dir), path.join(claudeDir, dir));
1138
- let removed = 0;
1139
- for (const f of templateFiles) {
1140
- if (fs.existsSync(f)) {
1141
- fs.unlinkSync(f);
1142
- removed++;
1143
- }
1144
- }
1145
- const dirPath = path.join(claudeDir, dir);
1146
- if (fs.existsSync(dirPath))
1147
- cleanEmptyDirs(dirPath);
1148
- console.log(` Removed ${removed} ${dir} files`);
1149
- }
1150
- console.log(`\nLegacy uninstall complete.`);
1151
- console.log(`Note: scopes/ and .claude/orbital-events/ were preserved.\n`);
1152
- }
1153
- /** Remove Orbital-added entries from .gitignore. */
1154
- function removeGitignoreEntries(projectRoot, entries) {
1155
- const gitignorePath = path.join(projectRoot, '.gitignore');
1156
- if (!fs.existsSync(gitignorePath))
1157
- return;
1158
- let content = fs.readFileSync(gitignorePath, 'utf-8');
1159
- const marker = '# Orbital Command';
1160
- // Try to remove the entire block
1161
- const markerIdx = content.indexOf(marker);
1162
- if (markerIdx !== -1) {
1163
- // Find the block boundaries: from the marker to the next non-empty/non-orbital line
1164
- const before = content.slice(0, markerIdx).replace(/\n+$/, '');
1165
- const after = content.slice(markerIdx);
1166
- const lines = after.split('\n');
1167
- let endIdx = 0;
1168
- for (let i = 0; i < lines.length; i++) {
1169
- const line = lines[i].trim();
1170
- if (i === 0) {
1171
- endIdx = i + 1;
1172
- continue;
1173
- } // skip the marker line
1174
- if (line === '' || entries.includes(line)) {
1175
- endIdx = i + 1;
1176
- continue;
1177
- }
1178
- break;
1179
- }
1180
- const remaining = lines.slice(endIdx).join('\n');
1181
- content = before + (remaining ? '\n' + remaining : '') + '\n';
1182
- fs.writeFileSync(gitignorePath, content, 'utf-8');
1183
- }
1184
- }
1185
- /** Copy a single template file to a destination, resolving the template path. */
1186
- function copyTemplateFile(templatesDir, claudeRelPath, destPath) {
1187
- const templateRelPath = reverseRemapPath(claudeRelPath);
1188
- const srcPath = path.join(templatesDir, templateRelPath);
1189
- if (!fs.existsSync(srcPath)) {
1190
- throw new Error(`Template file not found: ${templateRelPath}`);
1191
- }
1192
- const destDir = path.dirname(destPath);
1193
- if (!fs.existsSync(destDir))
1194
- fs.mkdirSync(destDir, { recursive: true });
1195
- safeCopyTemplate(srcPath, destPath);
1196
- }
828
+ // runUpdate and runUninstall have been extracted to update.ts and uninstall.ts
829
+ // and are re-exported above for backward compatibility.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Entry point for `orbital launch` — starts the central multi-project server.
2
+ * Entry point for the central multi-project server.
3
3
  *
4
4
  * Reads environment variables set by bin/orbital.js:
5
5
  * ORBITAL_LAUNCH_MODE=central
@@ -32,7 +32,10 @@ export function parseEventFile(filePath) {
32
32
  };
33
33
  }
34
34
  catch (err) {
35
- log.warn('Failed to parse event file', { file: filePath, error: err.message });
35
+ const code = err.code;
36
+ if (code !== 'ENOENT') {
37
+ log.warn('Failed to parse event file', { file: filePath, error: err.message });
38
+ }
36
39
  return null;
37
40
  }
38
41
  }
@@ -21,7 +21,6 @@ import { startScopeWatcher } from './watchers/scope-watcher.js';
21
21
  import { startEventWatcher } from './watchers/event-watcher.js';
22
22
  import { resolveStaleDispatches, resolveActiveDispatchesForScope, resolveDispatchesByPid, resolveDispatchesByDispatchId, linkPidToDispatch, tryAutoRevertAndClear } from './utils/dispatch-utils.js';
23
23
  import { syncClaudeSessionsToDB } from './services/claude-session-service.js';
24
- import { TelemetryService } from './services/telemetry-service.js';
25
24
  import { ensureDynamicProfiles } from './utils/terminal-launcher.js';
26
25
  import { createLogger } from './utils/logger.js';
27
26
  const log = createLogger('project-context');
@@ -60,8 +59,8 @@ export async function createProjectContext(projectId, projectRoot, emitter) {
60
59
  const gateService = new GateService(db, emitter);
61
60
  const deployService = new DeployService(db, emitter);
62
61
  const sprintService = new SprintService(db, emitter, scopeService);
63
- const sprintOrchestrator = new SprintOrchestrator(db, emitter, sprintService, scopeService, workflowEngine, config.projectRoot);
64
- const batchOrchestrator = new BatchOrchestrator(db, emitter, sprintService, scopeService, workflowEngine, config.projectRoot);
62
+ const sprintOrchestrator = new SprintOrchestrator(db, emitter, sprintService, scopeService, workflowEngine, config.projectRoot, config);
63
+ const batchOrchestrator = new BatchOrchestrator(db, emitter, sprintService, scopeService, workflowEngine, config.projectRoot, config);
65
64
  const readinessService = new ReadinessService(scopeService, gateService, workflowEngine, config.projectRoot);
66
65
  const workflowService = new WorkflowService(config.configDir, workflowEngine, config.scopesDir, getDefaultConfigPath());
67
66
  workflowService.setSocketServer(emitter);
@@ -69,7 +68,17 @@ export async function createProjectContext(projectId, projectRoot, emitter) {
69
68
  workflowEngine.reload(workflowService.getActive());
70
69
  const gitService = new GitService(config.projectRoot, scopeCache);
71
70
  const githubService = new GitHubService(config.projectRoot);
72
- const telemetryService = new TelemetryService(db, config.telemetry, config.projectName, config.projectRoot);
71
+ let telemetryService = null;
72
+ let telemetryRouter = null;
73
+ const telemetryMod = './services/telemetry-service.js';
74
+ try {
75
+ const mod = await import(telemetryMod);
76
+ telemetryService = new mod.TelemetryService(db, config.telemetry, config.projectName, config.projectRoot);
77
+ if (telemetryService?.enabled) {
78
+ telemetryRouter = mod.createTelemetryRoutes({ telemetryService });
79
+ }
80
+ }
81
+ catch { /* telemetry service not installed */ }
73
82
  // Wire active-group guard
74
83
  scopeService.setActiveGroupCheck((scopeId) => sprintService.getActiveGroupForScope(scopeId));
75
84
  // Wire event inference (Fix 8: diagnostic log lines match index.ts)
@@ -130,7 +139,7 @@ export async function createProjectContext(projectId, projectRoot, emitter) {
130
139
  });
131
140
  // Wire status change callbacks
132
141
  scopeService.onStatusChange((scopeId, newStatus) => {
133
- if (newStatus === 'dev')
142
+ if (workflowEngine.isTerminalStatus(newStatus))
134
143
  sprintOrchestrator.onScopeReachedDev(scopeId);
135
144
  batchOrchestrator.onScopeStatusChanged(scopeId, newStatus);
136
145
  });
@@ -157,7 +166,7 @@ export async function createProjectContext(projectId, projectRoot, emitter) {
157
166
  if (staleBatchesResolved > 0)
158
167
  log.info('Resolved stale batches', { count: staleBatchesResolved });
159
168
  // Resolve stale dispatches
160
- resolveStaleDispatches(db, emitter, scopeService, workflowEngine);
169
+ resolveStaleDispatches(db, emitter, scopeService, workflowEngine, config.dispatch.staleTimeoutMinutes);
161
170
  // Initial session sync + legacy purge (Fix 7)
162
171
  syncClaudeSessionsToDB(db, scopeService, config.projectRoot).then((count) => {
163
172
  if (count > 0)
@@ -165,7 +174,7 @@ export async function createProjectContext(projectId, projectRoot, emitter) {
165
174
  const purged = db.prepare("DELETE FROM sessions WHERE action IS NULL AND id LIKE 'claude-%'").run();
166
175
  if (purged.changes > 0)
167
176
  log.info('Purged legacy session rows', { count: purged.changes });
168
- if (telemetryService.enabled) {
177
+ if (telemetryService?.enabled) {
169
178
  telemetryService.uploadChangedSessions().catch(() => { });
170
179
  }
171
180
  }).catch(err => log.error('Session sync failed', { error: err.message }));
@@ -179,13 +188,13 @@ export async function createProjectContext(projectId, projectRoot, emitter) {
179
188
  batchOrchestrator.resolveStaleBatches();
180
189
  }, 30_000));
181
190
  intervals.push(setInterval(() => {
182
- resolveStaleDispatches(db, emitter, scopeService, workflowEngine);
191
+ resolveStaleDispatches(db, emitter, scopeService, workflowEngine, config.dispatch.staleTimeoutMinutes);
183
192
  }, 30_000));
184
193
  intervals.push(setInterval(async () => {
185
194
  const count = await syncClaudeSessionsToDB(db, scopeService, config.projectRoot);
186
195
  if (count > 0)
187
196
  emitter.emit('session:updated', { type: 'resync', count });
188
- if (telemetryService.enabled) {
197
+ if (telemetryService?.enabled) {
189
198
  telemetryService.uploadChangedSessions().catch(() => { });
190
199
  }
191
200
  }, 5 * 60_000));
@@ -221,6 +230,7 @@ export async function createProjectContext(projectId, projectRoot, emitter) {
221
230
  gitService,
222
231
  githubService,
223
232
  telemetryService,
233
+ telemetryRouter,
224
234
  scopeWatcher,
225
235
  eventWatcher,
226
236
  intervals,
@@ -11,7 +11,6 @@ import { createWorkflowRoutes } from './routes/workflow-routes.js';
11
11
  import { createConfigRoutes } from './routes/config-routes.js';
12
12
  import { createGitRoutes } from './routes/git-routes.js';
13
13
  import { createManifestRoutes } from './routes/manifest-routes.js';
14
- import { createTelemetryRoutes } from './services/telemetry-service.js';
15
14
  import { TEMPLATES_DIR } from './init.js';
16
15
  import { getPackageVersion } from './utils/package-info.js';
17
16
  import { resolveActiveDispatchesForScope } from './utils/dispatch-utils.js';
@@ -227,7 +226,7 @@ export class ProjectManager {
227
226
  * are global (they update the Orbital Command package), not per-project. */
228
227
  buildProjectRouter(ctx) {
229
228
  const router = Router();
230
- const { db, emitter, config, scopeService, eventService, gateService, deployService, sprintService, sprintOrchestrator, batchOrchestrator, readinessService, workflowService, workflowEngine, gitService, githubService, telemetryService } = ctx;
229
+ const { db, emitter, config, scopeService, eventService, gateService, deployService, sprintService, sprintOrchestrator, batchOrchestrator, readinessService, workflowService, workflowEngine, gitService, githubService } = ctx;
231
230
  // Scope status inference function (same logic as index.ts)
232
231
  function inferScopeStatus(eventType, scopeId, data) {
233
232
  if (scopeId == null)
@@ -262,16 +261,16 @@ export class ProjectManager {
262
261
  router.use(createScopeRoutes({
263
262
  db, io: emitter, scopeService, readinessService,
264
263
  projectRoot: config.projectRoot, projectName: config.projectName,
265
- engine: workflowEngine,
264
+ engine: workflowEngine, config,
266
265
  }));
267
266
  router.use(createDataRoutes({
268
267
  db, io: emitter, eventService, gateService, deployService, gitService,
269
268
  engine: workflowEngine, projectRoot: config.projectRoot,
270
- inferScopeStatus,
269
+ inferScopeStatus, config,
271
270
  }));
272
271
  router.use(createDispatchRoutes({
273
272
  db, io: emitter, scopeService,
274
- projectRoot: config.projectRoot, engine: workflowEngine,
273
+ projectRoot: config.projectRoot, engine: workflowEngine, config,
275
274
  }));
276
275
  router.use(createSprintRoutes({
277
276
  sprintService, sprintOrchestrator, batchOrchestrator,
@@ -291,7 +290,8 @@ export class ProjectManager {
291
290
  packageVersion: getPackageVersion(),
292
291
  io: emitter,
293
292
  }));
294
- router.use(createTelemetryRoutes({ telemetryService }));
293
+ if (ctx.telemetryRouter)
294
+ router.use(ctx.telemetryRouter);
295
295
  return router;
296
296
  }
297
297
  }