gsd-unsupervised 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 (83) hide show
  1. package/README.md +263 -0
  2. package/bin/gsd-unsupervised +3 -0
  3. package/bin/start-daemon.sh +12 -0
  4. package/bin/unsupervised-gsd +2 -0
  5. package/dist/agent-runner.d.ts +26 -0
  6. package/dist/agent-runner.js +111 -0
  7. package/dist/agent-runner.spawn.test.d.ts +1 -0
  8. package/dist/agent-runner.spawn.test.js +128 -0
  9. package/dist/agent-runner.test.d.ts +1 -0
  10. package/dist/agent-runner.test.js +26 -0
  11. package/dist/bootstrap/wsl-bootstrap.d.ts +11 -0
  12. package/dist/bootstrap/wsl-bootstrap.js +14 -0
  13. package/dist/cli.d.ts +1 -0
  14. package/dist/cli.js +172 -0
  15. package/dist/config/paths.d.ts +8 -0
  16. package/dist/config/paths.js +36 -0
  17. package/dist/config/wsl.d.ts +4 -0
  18. package/dist/config/wsl.js +43 -0
  19. package/dist/config.d.ts +79 -0
  20. package/dist/config.js +95 -0
  21. package/dist/config.test.d.ts +1 -0
  22. package/dist/config.test.js +27 -0
  23. package/dist/cursor-agent.d.ts +17 -0
  24. package/dist/cursor-agent.invoker.test.d.ts +1 -0
  25. package/dist/cursor-agent.invoker.test.js +150 -0
  26. package/dist/cursor-agent.js +156 -0
  27. package/dist/cursor-agent.test.d.ts +1 -0
  28. package/dist/cursor-agent.test.js +60 -0
  29. package/dist/daemon.d.ts +17 -0
  30. package/dist/daemon.js +374 -0
  31. package/dist/git.d.ts +23 -0
  32. package/dist/git.js +76 -0
  33. package/dist/goals.d.ts +34 -0
  34. package/dist/goals.js +148 -0
  35. package/dist/gsd-state.d.ts +49 -0
  36. package/dist/gsd-state.js +76 -0
  37. package/dist/init-wizard.d.ts +5 -0
  38. package/dist/init-wizard.js +96 -0
  39. package/dist/lifecycle.d.ts +41 -0
  40. package/dist/lifecycle.js +103 -0
  41. package/dist/lifecycle.test.d.ts +1 -0
  42. package/dist/lifecycle.test.js +116 -0
  43. package/dist/logger.d.ts +12 -0
  44. package/dist/logger.js +31 -0
  45. package/dist/notifier.d.ts +6 -0
  46. package/dist/notifier.js +37 -0
  47. package/dist/orchestrator.d.ts +35 -0
  48. package/dist/orchestrator.js +791 -0
  49. package/dist/resource-governor.d.ts +54 -0
  50. package/dist/resource-governor.js +57 -0
  51. package/dist/resource-governor.test.d.ts +1 -0
  52. package/dist/resource-governor.test.js +33 -0
  53. package/dist/resume-pointer.d.ts +36 -0
  54. package/dist/resume-pointer.js +116 -0
  55. package/dist/roadmap-parser.d.ts +24 -0
  56. package/dist/roadmap-parser.js +105 -0
  57. package/dist/roadmap-parser.test.d.ts +1 -0
  58. package/dist/roadmap-parser.test.js +57 -0
  59. package/dist/session-log.d.ts +53 -0
  60. package/dist/session-log.js +92 -0
  61. package/dist/session-log.test.d.ts +1 -0
  62. package/dist/session-log.test.js +146 -0
  63. package/dist/state-index.d.ts +5 -0
  64. package/dist/state-index.js +31 -0
  65. package/dist/state-parser.d.ts +13 -0
  66. package/dist/state-parser.js +82 -0
  67. package/dist/state-parser.test.d.ts +1 -0
  68. package/dist/state-parser.test.js +228 -0
  69. package/dist/state-types.d.ts +20 -0
  70. package/dist/state-types.js +1 -0
  71. package/dist/state-watcher.d.ts +49 -0
  72. package/dist/state-watcher.js +148 -0
  73. package/dist/status-server.d.ts +112 -0
  74. package/dist/status-server.js +379 -0
  75. package/dist/status-server.test.d.ts +1 -0
  76. package/dist/status-server.test.js +206 -0
  77. package/dist/stream-events.d.ts +423 -0
  78. package/dist/stream-events.js +87 -0
  79. package/dist/stream-events.test.d.ts +1 -0
  80. package/dist/stream-events.test.js +304 -0
  81. package/dist/todos-api.d.ts +5 -0
  82. package/dist/todos-api.js +35 -0
  83. package/package.json +54 -0
@@ -0,0 +1,116 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { GoalLifecyclePhase, GoalStateMachine, } from './lifecycle.js';
3
+ describe('GoalStateMachine transitions', () => {
4
+ it('starts in the new phase with zeroed counters', () => {
5
+ const sm = new GoalStateMachine('Test goal');
6
+ const progress = sm.getProgress();
7
+ expect(progress.phase).toBe(GoalLifecyclePhase.New);
8
+ expect(progress.currentPhaseNumber).toBe(0);
9
+ expect(progress.totalPhases).toBe(0);
10
+ expect(progress.currentPlanIndex).toBe(0);
11
+ expect(progress.totalPlansInPhase).toBe(0);
12
+ expect(sm.isTerminal()).toBe(false);
13
+ });
14
+ it('allows valid forward transitions based on LIFECYCLE_TRANSITIONS', () => {
15
+ const sm = new GoalStateMachine('Test goal');
16
+ // new -> initializing_project
17
+ sm.advance(GoalLifecyclePhase.InitializingProject);
18
+ expect(sm.getPhase()).toBe(GoalLifecyclePhase.InitializingProject);
19
+ // initializing_project -> creating_roadmap
20
+ sm.advance(GoalLifecyclePhase.CreatingRoadmap);
21
+ expect(sm.getPhase()).toBe(GoalLifecyclePhase.CreatingRoadmap);
22
+ // creating_roadmap -> planning_phase
23
+ sm.advance(GoalLifecyclePhase.PlanningPhase);
24
+ expect(sm.getPhase()).toBe(GoalLifecyclePhase.PlanningPhase);
25
+ // planning_phase -> executing_plan
26
+ sm.advance(GoalLifecyclePhase.ExecutingPlan);
27
+ expect(sm.getPhase()).toBe(GoalLifecyclePhase.ExecutingPlan);
28
+ // executing_plan can transition to itself (multiple plans in a phase)
29
+ sm.advance(GoalLifecyclePhase.ExecutingPlan);
30
+ expect(sm.getPhase()).toBe(GoalLifecyclePhase.ExecutingPlan);
31
+ // executing_plan -> phase_complete
32
+ sm.advance(GoalLifecyclePhase.PhaseComplete);
33
+ expect(sm.getPhase()).toBe(GoalLifecyclePhase.PhaseComplete);
34
+ // phase_complete -> complete
35
+ sm.advance(GoalLifecyclePhase.Complete);
36
+ expect(sm.getPhase()).toBe(GoalLifecyclePhase.Complete);
37
+ expect(sm.isTerminal()).toBe(true);
38
+ expect(sm.isComplete()).toBe(true);
39
+ });
40
+ it('throws helpful error for invalid transitions with current, target, and allowed set', () => {
41
+ const sm = new GoalStateMachine('Test goal');
42
+ expect(() => sm.advance(GoalLifecyclePhase.PlanningPhase)).toThrowError(/Invalid transition: new → planning_phase\. Allowed transitions from 'new': \[initializing_project]/);
43
+ });
44
+ it('moves to failed phase and records error when fail() is called', () => {
45
+ const sm = new GoalStateMachine('Test goal');
46
+ sm.fail('something went wrong');
47
+ const progress = sm.getProgress();
48
+ expect(progress.phase).toBe(GoalLifecyclePhase.Failed);
49
+ expect(progress.error).toBe('something went wrong');
50
+ expect(sm.isFailed()).toBe(true);
51
+ expect(sm.isTerminal()).toBe(true);
52
+ });
53
+ });
54
+ describe('GoalStateMachine getNextCommand', () => {
55
+ it('returns /gsd/new-project for the initial new phase', () => {
56
+ const sm = new GoalStateMachine('Test goal');
57
+ const cmd = sm.getNextCommand();
58
+ expect(cmd).not.toBeNull();
59
+ expect(cmd.command).toBe('/gsd/new-project');
60
+ expect(cmd.args).toBeUndefined();
61
+ expect(cmd.description).toMatch(/Initialize project/i);
62
+ });
63
+ it('returns /gsd/create-roadmap after initializing_project', () => {
64
+ const sm = new GoalStateMachine('Test goal');
65
+ sm.advance(GoalLifecyclePhase.InitializingProject);
66
+ const cmd = sm.getNextCommand();
67
+ expect(cmd).not.toBeNull();
68
+ expect(cmd.command).toBe('/gsd/create-roadmap');
69
+ expect(cmd.args).toBeUndefined();
70
+ expect(cmd.description).toMatch(/Create roadmap/i);
71
+ });
72
+ it('returns /gsd/plan-phase N in creating_roadmap using currentPhaseNumber', () => {
73
+ const sm = new GoalStateMachine('Test goal');
74
+ sm.advance(GoalLifecyclePhase.InitializingProject);
75
+ sm.advance(GoalLifecyclePhase.CreatingRoadmap);
76
+ sm.setPhaseInfo(1, 3);
77
+ const cmd = sm.getNextCommand();
78
+ expect(cmd).not.toBeNull();
79
+ expect(cmd.command).toBe('/gsd/plan-phase');
80
+ expect(cmd.args).toBe('1');
81
+ expect(cmd.description).toContain('phase 1');
82
+ });
83
+ it('returns null while in planning_phase or executing_plan', () => {
84
+ const sm = new GoalStateMachine('Test goal');
85
+ sm.advance(GoalLifecyclePhase.InitializingProject);
86
+ sm.advance(GoalLifecyclePhase.CreatingRoadmap);
87
+ sm.advance(GoalLifecyclePhase.PlanningPhase);
88
+ expect(sm.getNextCommand()).toBeNull();
89
+ sm.advance(GoalLifecyclePhase.ExecutingPlan);
90
+ expect(sm.getNextCommand()).toBeNull();
91
+ });
92
+ it('returns /gsd/plan-phase for the next phase when phase_complete and more phases remain', () => {
93
+ const sm = new GoalStateMachine('Test goal');
94
+ sm.advance(GoalLifecyclePhase.InitializingProject);
95
+ sm.advance(GoalLifecyclePhase.CreatingRoadmap);
96
+ sm.setPhaseInfo(1, 3);
97
+ sm.advance(GoalLifecyclePhase.PlanningPhase);
98
+ sm.advance(GoalLifecyclePhase.ExecutingPlan);
99
+ sm.advance(GoalLifecyclePhase.PhaseComplete);
100
+ const cmd = sm.getNextCommand();
101
+ expect(cmd).not.toBeNull();
102
+ expect(cmd.command).toBe('/gsd/plan-phase');
103
+ expect(cmd.args).toBe('2');
104
+ expect(cmd.description).toContain('phase 2');
105
+ });
106
+ it('returns null when phase_complete and already on the final phase', () => {
107
+ const sm = new GoalStateMachine('Test goal');
108
+ sm.advance(GoalLifecyclePhase.InitializingProject);
109
+ sm.advance(GoalLifecyclePhase.CreatingRoadmap);
110
+ sm.setPhaseInfo(3, 3);
111
+ sm.advance(GoalLifecyclePhase.PlanningPhase);
112
+ sm.advance(GoalLifecyclePhase.ExecutingPlan);
113
+ sm.advance(GoalLifecyclePhase.PhaseComplete);
114
+ expect(sm.getNextCommand()).toBeNull();
115
+ });
116
+ });
@@ -0,0 +1,12 @@
1
+ import { type Logger } from 'pino';
2
+ export type { Logger } from 'pino';
3
+ export declare function createLogger(options: {
4
+ level?: string;
5
+ pretty?: boolean;
6
+ }): Logger;
7
+ export declare function createChildLogger(parent: Logger, component: string): Logger;
8
+ export declare function initLogger(options: {
9
+ level?: string;
10
+ pretty?: boolean;
11
+ }): Logger;
12
+ export declare function getLogger(): Logger;
package/dist/logger.js ADDED
@@ -0,0 +1,31 @@
1
+ import pino from 'pino';
2
+ export function createLogger(options) {
3
+ const { level = 'info', pretty = false } = options;
4
+ if (pretty) {
5
+ return pino({
6
+ level,
7
+ transport: {
8
+ target: 'pino-pretty',
9
+ options: {
10
+ colorize: true,
11
+ translateTime: 'SYS:HH:MM:ss',
12
+ },
13
+ },
14
+ });
15
+ }
16
+ return pino({ level });
17
+ }
18
+ export function createChildLogger(parent, component) {
19
+ return parent.child({ component });
20
+ }
21
+ let defaultLogger;
22
+ export function initLogger(options) {
23
+ defaultLogger = createLogger(options);
24
+ return defaultLogger;
25
+ }
26
+ export function getLogger() {
27
+ if (!defaultLogger) {
28
+ throw new Error('Logger not initialized — call initLogger() first');
29
+ }
30
+ return defaultLogger;
31
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Twilio SMS outbound only (alerts on goal complete/fail/pause).
3
+ * No inbound SMS or webhook: adding goals or todos via SMS is not implemented.
4
+ * Reads TWILIO_* from process.env; single POST to Twilio Messages API.
5
+ */
6
+ export declare function sendSms(message: string): Promise<void>;
@@ -0,0 +1,37 @@
1
+ function getEnv(name) {
2
+ const v = process.env[name];
3
+ if (!v || v.trim() === '') {
4
+ throw new Error(`Missing required env var: ${name}`);
5
+ }
6
+ return v.trim();
7
+ }
8
+ /**
9
+ * Twilio SMS outbound only (alerts on goal complete/fail/pause).
10
+ * No inbound SMS or webhook: adding goals or todos via SMS is not implemented.
11
+ * Reads TWILIO_* from process.env; single POST to Twilio Messages API.
12
+ */
13
+ export async function sendSms(message) {
14
+ const accountSid = getEnv('TWILIO_ACCOUNT_SID');
15
+ const authToken = getEnv('TWILIO_AUTH_TOKEN');
16
+ const from = getEnv('TWILIO_FROM');
17
+ const to = getEnv('TWILIO_TO');
18
+ const url = `https://api.twilio.com/2010-04-01/Accounts/${encodeURIComponent(accountSid)}/Messages.json`;
19
+ const body = new URLSearchParams({
20
+ From: from,
21
+ To: to,
22
+ Body: message,
23
+ });
24
+ const auth = Buffer.from(`${accountSid}:${authToken}`, 'utf-8').toString('base64');
25
+ const res = await fetch(url, {
26
+ method: 'POST',
27
+ headers: {
28
+ Authorization: `Basic ${auth}`,
29
+ 'Content-Type': 'application/x-www-form-urlencoded',
30
+ },
31
+ body,
32
+ });
33
+ if (!res.ok) {
34
+ const text = await res.text().catch(() => '');
35
+ throw new Error(`Twilio SMS failed (${res.status} ${res.statusText})${text ? `: ${text}` : ''}`);
36
+ }
37
+ }
@@ -0,0 +1,35 @@
1
+ import type { AutopilotConfig } from './config.js';
2
+ import type { Logger } from './logger.js';
3
+ import type { Goal } from './goals.js';
4
+ import type { SessionLogContext } from './session-log.js';
5
+ import { type GsdCommand } from './lifecycle.js';
6
+ import type { StateSnapshot } from './state-types.js';
7
+ import type { ResumeFrom } from './session-log.js';
8
+ export declare function reportProgress(options: {
9
+ stateMdPath: string;
10
+ logger: Logger;
11
+ onProgress?: (snapshot: StateSnapshot) => void;
12
+ expectedPhase: number;
13
+ expectedSummaryPath?: string;
14
+ }): Promise<void>;
15
+ export interface AgentResult {
16
+ success: boolean;
17
+ output?: string;
18
+ error?: string;
19
+ }
20
+ export type AgentInvoker = (command: GsdCommand, workspaceDir: string, logger: Logger, logContext?: SessionLogContext) => Promise<AgentResult>;
21
+ export declare function orchestrateGoal(options: {
22
+ goal: Goal;
23
+ config: AutopilotConfig;
24
+ logger: Logger;
25
+ agent?: AgentInvoker;
26
+ isShuttingDown: () => boolean;
27
+ onProgress?: (snapshot: StateSnapshot) => void;
28
+ /** When set, orchestrator will resume from this phase/plan (used in 05-03). */
29
+ resumeFrom?: ResumeFrom | null;
30
+ /**
31
+ * Hint to skip re-running /gsd/plan-phase for phases before this 1-based phase index.
32
+ * Plans will still be discovered/executed for skipped phases.
33
+ */
34
+ skipToPhase?: number | null;
35
+ }): Promise<void>;