orbital-command 1.0.1 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +1 -1
  2. package/bin/commands/launch.js +0 -1
  3. package/bin/lib/helpers.js +1 -31
  4. package/bin/orbital.js +34 -32
  5. package/dist/assets/Landing-B6q9U0Vd.js +11 -0
  6. package/dist/assets/{PrimitivesConfig-DThSipFy.js → PrimitivesConfig-TlFvypvg.js} +9 -14
  7. package/dist/assets/{QualityGates-B4kxM5UU.js → QualityGates-D8uvclW4.js} +1 -1
  8. package/dist/assets/SessionTimeline-QUaJw6Aa.js +1 -0
  9. package/dist/assets/Settings-CAEnAZAk.js +12 -0
  10. package/dist/assets/{SourceControl-BMNIz7Lt.js → SourceControl-DPeSBaMV.js} +7 -12
  11. package/dist/assets/{WorkflowVisualizer-CxuSBOYu.js → WorkflowVisualizer-DHrIjx6W.js} +1 -1
  12. package/dist/assets/{arrow-down-DVPp6_qp.js → arrow-down-DnfKgF33.js} +1 -1
  13. package/dist/assets/{bot-NFaJBDn_.js → bot-DUPnHZfM.js} +1 -1
  14. package/dist/assets/{circle-x-IsFCkBZu.js → circle-x-B4nA79je.js} +1 -1
  15. package/dist/assets/{file-text-J1cebZXF.js → file-text-PnzRO4pO.js} +1 -1
  16. package/dist/assets/{globe-WzeyHsUc.js → globe-CIX_GBr0.js} +1 -1
  17. package/dist/assets/index-B-B-tTjw.css +1 -0
  18. package/dist/assets/index-DQVAzHMu.js +354 -0
  19. package/dist/assets/{key-CKR8JJSj.js → key-DpAZM-3d.js} +1 -1
  20. package/dist/assets/{minus-CHBsJyjp.js → minus-CJlpKNUB.js} +1 -1
  21. package/dist/assets/{radio-xqZaR-Uk.js → radio-DhynLcSx.js} +1 -1
  22. package/dist/assets/{rocket-D_xvvNG6.js → rocket-DnuQh3sF.js} +1 -1
  23. package/dist/assets/{shield-TdB1yv_a.js → shield-DLMQmvFQ.js} +1 -1
  24. package/dist/assets/{ui-BmsSg9jU.js → ui-QhrRH5wk.js} +6 -6
  25. package/dist/assets/{useSocketListener-0L5yiN5i.js → useSocketListener-DPrExNDV.js} +1 -1
  26. package/dist/assets/{useWorkflowEditor-CqeRWVQX.js → useWorkflowEditor-BxN7phfr.js} +2 -2
  27. package/dist/assets/{workflow-constants-Rw-GmgHZ.js → workflow-constants-ZcCN11vm.js} +1 -1
  28. package/dist/assets/{zap-C9wqYMpl.js → zap-eJ3z8HRI.js} +1 -1
  29. package/dist/index.html +3 -3
  30. package/dist/server/server/index.js +9 -10
  31. package/dist/server/server/launch.js +0 -3
  32. package/dist/server/server/routes/sync-routes.js +175 -0
  33. package/dist/server/server/services/telemetry-service.js +143 -0
  34. package/dist/server/server/wizard/detect.js +0 -79
  35. package/dist/server/server/wizard/index.js +43 -113
  36. package/dist/server/server/wizard/phases/setup-wizard.js +4 -34
  37. package/dist/server/server/wizard/types.js +3 -25
  38. package/dist/server/shared/workflow-presets.js +22 -0
  39. package/package.json +1 -1
  40. package/server/index.ts +7 -14
  41. package/server/launch.ts +0 -3
  42. package/server/routes/sync-routes.ts +205 -0
  43. package/server/wizard/detect.ts +1 -81
  44. package/server/wizard/index.ts +45 -146
  45. package/server/wizard/phases/setup-wizard.ts +7 -38
  46. package/server/wizard/types.ts +4 -45
  47. package/shared/workflow-presets.ts +28 -0
  48. package/templates/hooks/block-push.sh +16 -2
  49. package/templates/hooks/git-commit-guard.sh +3 -2
  50. package/dist/assets/Landing-CfQdHR0N.js +0 -11
  51. package/dist/assets/SessionTimeline-Bz1iZnmg.js +0 -1
  52. package/dist/assets/Settings-DLcZwbCT.js +0 -12
  53. package/dist/assets/index-BdJ57EhC.css +0 -1
  54. package/dist/assets/index-o4ScMAuR.js +0 -349
  55. package/dist/server/server/wizard/phases/confirm.js +0 -39
  56. package/dist/server/server/wizard/phases/project-setup.js +0 -90
  57. package/dist/server/server/wizard/phases/welcome.js +0 -32
  58. package/dist/server/server/wizard/phases/workflow-setup.js +0 -22
  59. package/server/wizard/phases/confirm.ts +0 -45
  60. package/server/wizard/phases/project-setup.ts +0 -106
  61. package/server/wizard/phases/welcome.ts +0 -39
  62. package/server/wizard/phases/workflow-setup.ts +0 -28
@@ -4,16 +4,12 @@
4
4
 
5
5
  import fs from 'fs';
6
6
  import path from 'path';
7
- import type { SetupState, ProjectSetupState } from './types.js';
7
+ import type { SetupState } from './types.js';
8
8
 
9
9
  const ORBITAL_HOME = path.join(process.env.HOME || process.env.USERPROFILE || '~', '.orbital');
10
10
 
11
11
  export { ORBITAL_HOME };
12
12
 
13
- export function isInteractiveTerminal(): boolean {
14
- return !!(process.stdout.isTTY && !process.env.CI);
15
- }
16
-
17
13
  export function isOrbitalSetupDone(): boolean {
18
14
  return fs.existsSync(path.join(ORBITAL_HOME, 'config.json'));
19
15
  }
@@ -26,79 +22,3 @@ export function buildSetupState(packageVersion: string): SetupState {
26
22
  };
27
23
  }
28
24
 
29
- export function buildProjectState(projectRoot: string, packageVersion: string): ProjectSetupState {
30
- const projectConfigExists = fs.existsSync(path.join(projectRoot, '.claude', 'orbital.config.json'));
31
-
32
- return {
33
- projectRoot,
34
- isProjectInitialized: projectConfigExists,
35
- packageVersion,
36
- };
37
- }
38
-
39
- export function detectProjectName(projectRoot: string): string {
40
- return path.basename(projectRoot)
41
- .replace(/[-_]+/g, ' ')
42
- .replace(/\b\w/g, c => c.toUpperCase());
43
- }
44
-
45
- export function detectCommands(projectRoot: string): Record<string, string | null> {
46
- const commands: Record<string, string | null> = {
47
- typeCheck: null,
48
- lint: null,
49
- build: null,
50
- test: null,
51
- };
52
-
53
- const pkgJsonPath = path.join(projectRoot, 'package.json');
54
- if (!fs.existsSync(pkgJsonPath)) return commands;
55
-
56
- try {
57
- const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
58
- const scripts = pkg.scripts || {};
59
-
60
- if (scripts.typecheck || scripts['type-check']) {
61
- commands.typeCheck = `npm run ${scripts.typecheck ? 'typecheck' : 'type-check'}`;
62
- }
63
- if (scripts.lint) commands.lint = 'npm run lint';
64
- if (scripts.build) commands.build = 'npm run build';
65
- if (scripts.test) commands.test = 'npm run test';
66
- } catch { /* ignore malformed package.json */ }
67
-
68
- return commands;
69
- }
70
-
71
- export function detectPortConflict(serverPort: number): string | null {
72
- const registryPath = path.join(ORBITAL_HOME, 'config.json');
73
- if (!fs.existsSync(registryPath)) return null;
74
-
75
- try {
76
- const registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
77
- for (const project of registry.projects || []) {
78
- const configPath = path.join(project.path, '.claude', 'orbital.config.json');
79
- if (!fs.existsSync(configPath)) continue;
80
- try {
81
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
82
- if (config.serverPort === serverPort) return project.name;
83
- } catch { /* skip unreadable configs */ }
84
- }
85
- } catch { /* skip unreadable registry */ }
86
-
87
- return null;
88
- }
89
-
90
- export function isValidProjectPath(p: string): string | undefined {
91
- const resolved = p.startsWith('~')
92
- ? path.join(process.env.HOME || process.env.USERPROFILE || '~', p.slice(1))
93
- : path.resolve(p);
94
- if (!fs.existsSync(resolved)) return 'Directory does not exist';
95
- if (!fs.statSync(resolved).isDirectory()) return 'Not a directory';
96
- return undefined;
97
- }
98
-
99
- export function resolveProjectPath(p: string): string {
100
- if (p.startsWith('~')) {
101
- return path.join(process.env.HOME || process.env.USERPROFILE || '~', p.slice(1));
102
- }
103
- return path.resolve(p);
104
- }
@@ -2,8 +2,8 @@
2
2
  * Interactive CLI wizard — main orchestrator.
3
3
  *
4
4
  * Entry points:
5
- * runSetupWizard() — Phase 1: first-time Orbital setup (~/.orbital/)
6
- * runProjectSetup() Phase 2: per-project scaffolding (.claude/)
5
+ * runSetupWizard() — First-time Orbital setup (~/.orbital/)
6
+ * runHub() Context-aware hub menu (orbital)
7
7
  * runConfigEditor() — interactive config editor (orbital config)
8
8
  * runDoctor() — health diagnostics (orbital doctor)
9
9
  */
@@ -13,25 +13,19 @@ import path from 'path';
13
13
  import { spawn, execFileSync } from 'child_process';
14
14
  import * as p from '@clack/prompts';
15
15
  import pc from 'picocolors';
16
- import { buildSetupState, buildProjectState } from './detect.js';
16
+ import { buildSetupState } from './detect.js';
17
17
  import { phaseSetupWizard } from './phases/setup-wizard.js';
18
- import { phaseWelcome } from './phases/welcome.js';
19
- import { phaseProjectSetup } from './phases/project-setup.js';
20
- import { phaseWorkflowSetup } from './phases/workflow-setup.js';
21
- import { phaseConfirm, showPostInstall } from './phases/confirm.js';
22
- import { NOTES } from './ui.js';
23
18
  import { runConfigEditor } from './config-editor.js';
24
19
  import { runDoctor } from './doctor.js';
25
20
  import { isITerm2Available } from '../adapters/iterm2-adapter.js';
26
- import { registerProject } from '../global-config.js';
27
21
 
28
22
  export { runConfigEditor, runDoctor };
29
23
 
30
24
  // ─── Phase 1: Setup Wizard ─────────────────────────────────────
31
25
 
32
26
  /**
33
- * First-time setup. Creates ~/.orbital/, seeds primitives,
34
- * optionally links projects (running Phase 2 for each).
27
+ * First-time setup. Creates ~/.orbital/ and seeds primitives.
28
+ * Project setup is now handled by the frontend Add Project modal.
35
29
  */
36
30
  export async function runSetupWizard(packageVersion: string): Promise<void> {
37
31
  const state = buildSetupState(packageVersion);
@@ -40,94 +34,7 @@ export async function runSetupWizard(packageVersion: string): Promise<void> {
40
34
 
41
35
  await phaseSetupWizard(state);
42
36
 
43
- // If user linked projects, run Phase 2 for each
44
- for (const projectRoot of state.linkedProjects) {
45
- p.log.step(`Setting up ${pc.cyan(path.basename(projectRoot))}...`);
46
- await runProjectSetupInline(projectRoot, packageVersion);
47
- }
48
-
49
- if (state.linkedProjects.length === 0) {
50
- p.note(NOTES.setupComplete, 'Done');
51
- }
52
-
53
- p.outro(
54
- state.linkedProjects.length > 0
55
- ? `Run ${pc.cyan('orbital')} to launch the dashboard.`
56
- : `Run ${pc.cyan('orbital')} in a project directory to get started.`
57
- );
58
- }
59
-
60
- // ─── Phase 2: Project Setup ────────────────────────────────────
61
-
62
- /**
63
- * Per-project setup. Walks through name, commands, workflow, then
64
- * calls runInit() to scaffold files into .claude/.
65
- */
66
- export async function runProjectSetup(projectRoot: string, packageVersion: string, args: string[]): Promise<void> {
67
- const state = buildProjectState(projectRoot, packageVersion);
68
- const force = args.includes('--force');
69
-
70
- p.intro(`${pc.bgCyan(pc.black(' Orbital Command '))} ${pc.dim(`v${packageVersion}`)}`);
71
-
72
- // Welcome gate: detect re-init / reconfigure
73
- const forceFromWelcome = await phaseWelcome(state);
74
- const useForce = force || forceFromWelcome;
75
-
76
- await runProjectPhases(state, useForce);
77
-
78
- p.outro(`Run ${pc.cyan('orbital')} to launch the dashboard.`);
79
- }
80
-
81
- // ─── Shared project phases (used by both flows) ────────────────
82
-
83
- /**
84
- * Run the project setup phases and install. Used by both
85
- * standalone runProjectSetup() and inline from runSetupWizard().
86
- */
87
- async function runProjectPhases(state: ReturnType<typeof buildProjectState>, useForce: boolean): Promise<void> {
88
- await phaseProjectSetup(state);
89
- await phaseWorkflowSetup(state);
90
- await phaseConfirm(state);
91
-
92
- // Install
93
- const s = p.spinner();
94
- s.start('Installing into project...');
95
-
96
- try {
97
- const { runInit } = await import('../init.js');
98
-
99
- runInit(state.projectRoot, {
100
- force: useForce,
101
- quiet: true,
102
- preset: state.workflowPreset,
103
- projectName: state.projectName,
104
- serverPort: state.serverPort,
105
- clientPort: state.clientPort,
106
- commands: state.selectedCommands,
107
- });
108
-
109
- registerProject(state.projectRoot, { name: state.projectName });
110
- stampTemplateVersion(state.projectRoot, state.packageVersion);
111
-
112
- s.stop('Project ready.');
113
- } catch (err) {
114
- s.stop('Installation failed.');
115
- p.log.error(err instanceof Error ? err.message : String(err));
116
- process.exit(1);
117
- }
118
-
119
- showPostInstall(state);
120
- }
121
-
122
- /**
123
- * Inline project setup — called from Phase 1 when user links a project.
124
- * Skips intro/outro since the setup wizard already has those.
125
- */
126
- async function runProjectSetupInline(projectRoot: string, packageVersion: string): Promise<void> {
127
- const state = buildProjectState(projectRoot, packageVersion);
128
-
129
- // Skip welcome gate for inline — this is a fresh project being linked
130
- await runProjectPhases(state, false);
37
+ p.outro('Launching dashboard...');
131
38
  }
132
39
 
133
40
  // ─── Update Check ─────────────────────────────────────────────
@@ -138,6 +45,18 @@ interface UpdateInfo {
138
45
  isOutdated: boolean;
139
46
  }
140
47
 
48
+ /** Returns true if `a` is older than `b` (semver comparison). */
49
+ function isOlderThan(a: string, b: string): boolean {
50
+ const pa = a.match(/^(\d+)\.(\d+)\.(\d+)/);
51
+ const pb = b.match(/^(\d+)\.(\d+)\.(\d+)/);
52
+ if (!pa || !pb) return false;
53
+ for (let i = 1; i <= 3; i++) {
54
+ if (parseInt(pa[i]) < parseInt(pb[i])) return true;
55
+ if (parseInt(pa[i]) > parseInt(pb[i])) return false;
56
+ }
57
+ return false;
58
+ }
59
+
141
60
  async function checkForUpdate(
142
61
  currentVersion: string,
143
62
  cache: { lastUpdateCheck?: string; latestVersion?: string },
@@ -146,7 +65,7 @@ async function checkForUpdate(
146
65
  if (cache.lastUpdateCheck && cache.latestVersion) {
147
66
  const age = Date.now() - new Date(cache.lastUpdateCheck).getTime();
148
67
  if (age < 24 * 60 * 60 * 1000) {
149
- const isOutdated = cache.latestVersion !== currentVersion;
68
+ const isOutdated = isOlderThan(currentVersion, cache.latestVersion);
150
69
  return {
151
70
  info: { current: currentVersion, latest: cache.latestVersion, isOutdated },
152
71
  cacheChanged: false,
@@ -162,7 +81,7 @@ async function checkForUpdate(
162
81
  const data = await res.json() as { version: string };
163
82
  const latest = data.version;
164
83
  return {
165
- info: { current: currentVersion, latest, isOutdated: latest !== currentVersion },
84
+ info: { current: currentVersion, latest, isOutdated: isOlderThan(currentVersion, latest) },
166
85
  cacheChanged: true,
167
86
  };
168
87
  } catch {
@@ -172,7 +91,7 @@ async function checkForUpdate(
172
91
 
173
92
  // ─── Hub Menu ─────────────────────────────────────────────────
174
93
 
175
- export type HubAction = 'launch' | 'init' | 'config' | 'doctor' | 'update' | 'status' | 'reset';
94
+ export type HubAction = 'launch' | 'config' | 'doctor' | 'update' | 'status' | 'reset';
176
95
 
177
96
  export interface HubResult {
178
97
  action: HubAction;
@@ -186,7 +105,6 @@ export interface HubResult {
186
105
  */
187
106
  export async function runHub(opts: {
188
107
  packageVersion: string;
189
- isProjectInitialized: boolean;
190
108
  projectNames: string[];
191
109
  itermPromptShown: boolean;
192
110
  isMac: boolean;
@@ -357,28 +275,28 @@ export async function runHub(opts: {
357
275
  }
358
276
  }
359
277
 
360
- // ── Build menu options based on project state ──
361
- const projectHint = opts.projectNames.length > 0
362
- ? pc.dim(` (${opts.projectNames.join(', ')})`)
363
- : '';
278
+ // ── Show menu and pick action ──
279
+ result.action = await promptHubAction(opts.projectNames);
280
+ return result;
281
+ }
364
282
 
365
- const options: Array<{ value: HubAction; label: string; hint?: string }> = [];
283
+ /**
284
+ * Show the hub menu and return the chosen action.
285
+ * Exported separately so the CLI can loop back after executing an action.
286
+ */
287
+ export async function promptHubAction(projectNames: string[]): Promise<HubAction> {
288
+ const projectHint = projectNames.length > 0
289
+ ? pc.dim(` (${projectNames.join(', ')})`)
290
+ : '';
366
291
 
367
- if (opts.isProjectInitialized) {
368
- options.push(
369
- { value: 'launch', label: `Launch dashboard${projectHint}` },
370
- { value: 'config', label: 'Config', hint: 'modify project settings' },
371
- { value: 'doctor', label: 'Doctor', hint: 'health check & diagnostics' },
372
- { value: 'update', label: 'Update templates', hint: 'sync to latest' },
373
- { value: 'status', label: 'Status', hint: 'template sync status' },
374
- { value: 'reset', label: 'Reset to defaults', hint: 'force-reset all templates' },
375
- );
376
- } else {
377
- options.push(
378
- { value: 'init', label: 'Initialize this project' },
379
- { value: 'launch', label: `Launch dashboard${projectHint}` },
380
- );
381
- }
292
+ const options: Array<{ value: HubAction; label: string; hint?: string }> = [
293
+ { value: 'launch', label: `Launch dashboard${projectHint}` },
294
+ { value: 'config', label: 'Config', hint: 'modify project settings' },
295
+ { value: 'doctor', label: 'Doctor', hint: 'health check & diagnostics' },
296
+ { value: 'update', label: 'Update templates', hint: 'sync to latest' },
297
+ { value: 'status', label: 'Status', hint: 'template sync status' },
298
+ { value: 'reset', label: 'Reset to defaults', hint: 'force-reset all templates' },
299
+ ];
382
300
 
383
301
  const action = await p.select({
384
302
  message: 'What would you like to do?',
@@ -390,7 +308,7 @@ export async function runHub(opts: {
390
308
  process.exit(0);
391
309
  }
392
310
 
393
- // ── Double-confirm for destructive reset ──
311
+ // Double-confirm for destructive reset
394
312
  if (action === 'reset') {
395
313
  p.note(
396
314
  'This will overwrite ALL hooks, skills, agents, and workflow config\n' +
@@ -403,36 +321,17 @@ export async function runHub(opts: {
403
321
  initialValue: false,
404
322
  });
405
323
  if (p.isCancel(confirmReset) || !confirmReset) {
406
- p.cancel('Reset cancelled.');
407
- process.exit(0);
324
+ return promptHubAction(projectNames);
408
325
  }
409
326
  const doubleConfirm = await p.confirm({
410
327
  message: 'This cannot be undone. Continue?',
411
328
  initialValue: false,
412
329
  });
413
330
  if (p.isCancel(doubleConfirm) || !doubleConfirm) {
414
- p.cancel('Reset cancelled.');
415
- process.exit(0);
331
+ return promptHubAction(projectNames);
416
332
  }
417
333
  }
418
334
 
419
- result.action = action;
420
- return result;
335
+ return action;
421
336
  }
422
337
 
423
- // ─── Template Version Stamping ─────────────────────────────────
424
-
425
- function stampTemplateVersion(projectRoot: string, packageVersion: string): void {
426
- const configPath = path.join(projectRoot, '.claude', 'orbital.config.json');
427
- if (!fs.existsSync(configPath)) return;
428
-
429
- try {
430
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
431
- if (config.templateVersion !== packageVersion) {
432
- config.templateVersion = packageVersion;
433
- const tmp = configPath + `.tmp.${process.pid}`;
434
- fs.writeFileSync(tmp, JSON.stringify(config, null, 2) + '\n', 'utf8');
435
- fs.renameSync(tmp, configPath);
436
- }
437
- } catch { /* ignore malformed config */ }
438
- }
@@ -7,12 +7,11 @@
7
7
 
8
8
  import fs from 'fs';
9
9
  import * as p from '@clack/prompts';
10
- import pc from 'picocolors';
11
10
  import type { SetupState } from '../types.js';
12
11
  import { NOTES } from '../ui.js';
13
- import { isValidProjectPath, resolveProjectPath, ORBITAL_HOME } from '../detect.js';
12
+ import { ORBITAL_HOME } from '../detect.js';
14
13
 
15
- export async function phaseSetupWizard(state: SetupState): Promise<void> {
14
+ export async function phaseSetupWizard(_state: SetupState): Promise<void> {
16
15
  // Welcome and core concepts
17
16
  p.note(NOTES.setupWelcome, 'Welcome');
18
17
 
@@ -40,39 +39,9 @@ export async function phaseSetupWizard(state: SetupState): Promise<void> {
40
39
  process.exit(1);
41
40
  }
42
41
 
43
- // Offer to link projects
44
- p.note(NOTES.addProject, 'Projects');
45
-
46
- let addMore = true;
47
- while (addMore) {
48
- const wantsProject = await p.confirm({
49
- message: state.linkedProjects.length === 0
50
- ? 'Add a project now?'
51
- : 'Add another project?',
52
- initialValue: state.linkedProjects.length === 0,
53
- });
54
-
55
- if (p.isCancel(wantsProject) || !wantsProject) {
56
- addMore = false;
57
- break;
58
- }
59
-
60
- const projectPath = await p.text({
61
- message: 'Project path',
62
- placeholder: '~/Code/my-project',
63
- validate: (val) => {
64
- if (!val || !val.trim()) return 'Path is required';
65
- return isValidProjectPath(val.trim());
66
- },
67
- });
68
-
69
- if (p.isCancel(projectPath)) {
70
- addMore = false;
71
- break;
72
- }
73
-
74
- const resolved = resolveProjectPath(projectPath.trim());
75
- state.linkedProjects.push(resolved);
76
- p.log.success(`Added: ${pc.cyan(resolved)}`);
77
- }
42
+ // Direct user to the dashboard for project setup
43
+ p.note(
44
+ 'Launch the dashboard to add your first project.\nThe setup wizard will guide you through it.',
45
+ 'Next Steps',
46
+ );
78
47
  }
@@ -1,9 +1,8 @@
1
1
  /**
2
2
  * Types for the interactive CLI wizard.
3
3
  *
4
- * Two wizard flows:
5
- * Phase 1 (SetupState) — first-time Orbital setup, ~/.orbital/ creation
6
- * Phase 2 (ProjectSetupState) — per-project scaffolding into .claude/
4
+ * Phase 1 (SetupState) — first-time Orbital setup, ~/.orbital/ creation.
5
+ * Project setup is handled by the frontend Add Project modal.
7
6
  */
8
7
 
9
8
  export interface SetupState {
@@ -12,45 +11,5 @@ export interface SetupState {
12
11
  linkedProjects: string[]; // project paths added during setup
13
12
  }
14
13
 
15
- export interface ProjectSetupState {
16
- projectRoot: string;
17
- isProjectInitialized: boolean; // .claude/orbital.config.json exists
18
- packageVersion: string;
19
-
20
- // Collected from phases
21
- projectName?: string;
22
- serverPort?: number;
23
- clientPort?: number;
24
- detectedCommands?: Record<string, string | null>;
25
- selectedCommands?: Record<string, string | null>;
26
- workflowPreset?: string; // 'default' | 'minimal' | 'development' | 'gitflow'
27
- }
28
-
29
- export interface PresetInfo {
30
- value: string;
31
- label: string;
32
- hint: string;
33
- }
34
-
35
- export const WORKFLOW_PRESETS: PresetInfo[] = [
36
- {
37
- value: 'default',
38
- label: 'Default',
39
- hint: '7 lists, trunk-based — Icebox → Planning → Backlog → Implementing → Review → Completed → Main',
40
- },
41
- {
42
- value: 'minimal',
43
- label: 'Minimal',
44
- hint: '3 lists — To Do → In Progress → Done',
45
- },
46
- {
47
- value: 'development',
48
- label: 'Development',
49
- hint: '5 lists, dev branch — Backlog → Implementing → Review → Completed → Dev',
50
- },
51
- {
52
- value: 'gitflow',
53
- label: 'Gitflow',
54
- hint: '9 lists, multi-branch — Full pipeline with Dev, Staging, and Production',
55
- },
56
- ];
14
+ export type { PresetInfo } from '../../shared/workflow-presets.js';
15
+ export { WORKFLOW_PRESETS } from '../../shared/workflow-presets.js';
@@ -0,0 +1,28 @@
1
+ export interface PresetInfo {
2
+ value: string;
3
+ label: string;
4
+ hint: string;
5
+ }
6
+
7
+ export const WORKFLOW_PRESETS: PresetInfo[] = [
8
+ {
9
+ value: 'default',
10
+ label: 'Default',
11
+ hint: '7 lists, trunk-based — Icebox → Planning → Backlog → Implementing → Review → Completed → Main',
12
+ },
13
+ {
14
+ value: 'minimal',
15
+ label: 'Minimal',
16
+ hint: '3 lists — To Do → In Progress → Done',
17
+ },
18
+ {
19
+ value: 'development',
20
+ label: 'Development',
21
+ hint: '5 lists, dev branch — Backlog → Implementing → Review → Completed → Dev',
22
+ },
23
+ {
24
+ value: 'gitflow',
25
+ label: 'Gitflow',
26
+ hint: '9 lists, multi-branch — Full pipeline with Dev, Staging, and Production',
27
+ },
28
+ ];
@@ -37,8 +37,22 @@ _pipeline_after() {
37
37
  echo "$result"
38
38
  }
39
39
 
40
+ # Check if a flag file's session is still alive; remove stale flags
41
+ _flag_alive() {
42
+ local flag="$1"
43
+ [ -f "$flag" ] || return 1
44
+ local pid
45
+ pid=$(cat "$flag" 2>/dev/null)
46
+ # Empty flag (legacy touch-style) or dead process → stale
47
+ if [ -z "$pid" ] || ! kill -0 "$pid" 2>/dev/null; then
48
+ rm -f "$flag"
49
+ return 1
50
+ fi
51
+ return 0
52
+ }
53
+
40
54
  # Block git push during /git-commit
41
- if [ -f "$PUSH_FLAG" ]; then
55
+ if _flag_alive "$PUSH_FLAG"; then
42
56
  if echo "$COMMAND" | grep -qE '(^|[;&|]\s*)git\s+push'; then
43
57
  "$HOOK_DIR/orbital-emit.sh" VIOLATION '{"rule":"block-push","outcome":"blocked"}' 2>/dev/null || true
44
58
  REMAINING=$(_pipeline_after "completed")
@@ -51,7 +65,7 @@ if [ -f "$PUSH_FLAG" ]; then
51
65
  fi
52
66
 
53
67
  # Block git commit/add during /scope-implement
54
- if [ -f "$IMPL_FLAG" ]; then
68
+ if _flag_alive "$IMPL_FLAG"; then
55
69
  if echo "$COMMAND" | grep -qE '(^|[;&|]\s*)git\s+(commit|add)'; then
56
70
  "$HOOK_DIR/orbital-emit.sh" VIOLATION '{"rule":"block-commit-implementing","outcome":"blocked"}' 2>/dev/null || true
57
71
  REMAINING=$(_pipeline_after "implementing")
@@ -18,15 +18,16 @@ PUSH_FLAG="$PROJECT_DIR/.claude/.block-push-active"
18
18
  IMPL_FLAG="$PROJECT_DIR/.claude/.implementing-session"
19
19
 
20
20
  # /git-commit → block pushes during commit skill
21
+ # Store parent PID so block-push.sh can detect stale flags from dead sessions
21
22
  if [ "$SKILL" = "git-commit" ]; then
22
- touch "$PUSH_FLAG"
23
+ echo "$PPID" > "$PUSH_FLAG"
23
24
  else
24
25
  rm -f "$PUSH_FLAG"
25
26
  fi
26
27
 
27
28
  # /scope-implement → block commits during implementing
28
29
  if [ "$SKILL" = "scope-implement" ]; then
29
- touch "$IMPL_FLAG"
30
+ echo "$PPID" > "$IMPL_FLAG"
30
31
  else
31
32
  rm -f "$IMPL_FLAG"
32
33
  fi