wiggum-cli 0.16.0 → 0.17.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 (97) hide show
  1. package/bin/ralph.js +0 -0
  2. package/dist/agent/memory/ingest.d.ts +14 -0
  3. package/dist/agent/memory/ingest.js +77 -0
  4. package/dist/agent/memory/store.d.ts +15 -0
  5. package/dist/agent/memory/store.js +98 -0
  6. package/dist/agent/memory/types.d.ts +16 -0
  7. package/dist/agent/memory/types.js +14 -0
  8. package/dist/agent/orchestrator.d.ts +7 -0
  9. package/dist/agent/orchestrator.js +266 -0
  10. package/dist/agent/resolve-config.d.ts +26 -0
  11. package/dist/agent/resolve-config.js +43 -0
  12. package/dist/agent/tools/backlog.d.ts +27 -0
  13. package/dist/agent/tools/backlog.js +51 -0
  14. package/dist/agent/tools/dry-run.d.ts +106 -0
  15. package/dist/agent/tools/dry-run.js +119 -0
  16. package/dist/agent/tools/execution.d.ts +51 -0
  17. package/dist/agent/tools/execution.js +256 -0
  18. package/dist/agent/tools/feature-state.d.ts +43 -0
  19. package/dist/agent/tools/feature-state.js +184 -0
  20. package/dist/agent/tools/introspection.d.ts +23 -0
  21. package/dist/agent/tools/introspection.js +40 -0
  22. package/dist/agent/tools/memory.d.ts +44 -0
  23. package/dist/agent/tools/memory.js +99 -0
  24. package/dist/agent/tools/preflight.d.ts +7 -0
  25. package/dist/agent/tools/preflight.js +137 -0
  26. package/dist/agent/tools/reporting.d.ts +58 -0
  27. package/dist/agent/tools/reporting.js +119 -0
  28. package/dist/agent/tools/schemas.d.ts +2 -0
  29. package/dist/agent/tools/schemas.js +3 -0
  30. package/dist/agent/types.d.ts +45 -0
  31. package/dist/agent/types.js +1 -0
  32. package/dist/ai/conversation/conversation-manager.js +8 -0
  33. package/dist/ai/conversation/url-fetcher.js +27 -0
  34. package/dist/ai/providers.js +5 -5
  35. package/dist/commands/agent.d.ts +17 -0
  36. package/dist/commands/agent.js +114 -0
  37. package/dist/commands/monitor.js +50 -183
  38. package/dist/commands/new-auto.d.ts +15 -0
  39. package/dist/commands/new-auto.js +237 -0
  40. package/dist/commands/run.js +20 -10
  41. package/dist/commands/sync.d.ts +15 -0
  42. package/dist/commands/sync.js +68 -0
  43. package/dist/generator/config.d.ts +1 -41
  44. package/dist/generator/config.js +7 -0
  45. package/dist/generator/index.d.ts +2 -2
  46. package/dist/generator/templates.d.ts +2 -0
  47. package/dist/generator/templates.js +9 -1
  48. package/dist/index.d.ts +1 -1
  49. package/dist/index.js +115 -4
  50. package/dist/repl/command-parser.d.ts +5 -0
  51. package/dist/repl/command-parser.js +5 -0
  52. package/dist/templates/prompts/PROMPT.md.tmpl +13 -10
  53. package/dist/templates/prompts/PROMPT_e2e.md.tmpl +13 -7
  54. package/dist/templates/prompts/PROMPT_feature.md.tmpl +16 -3
  55. package/dist/templates/prompts/PROMPT_review_auto.md.tmpl +32 -12
  56. package/dist/templates/prompts/PROMPT_review_manual.md.tmpl +4 -1
  57. package/dist/templates/prompts/PROMPT_review_merge.md.tmpl +39 -14
  58. package/dist/templates/prompts/PROMPT_verify.md.tmpl +5 -2
  59. package/dist/templates/scripts/feature-loop.sh.tmpl +441 -69
  60. package/dist/tui/app.d.ts +19 -2
  61. package/dist/tui/app.js +22 -4
  62. package/dist/tui/components/IssuePicker.d.ts +27 -0
  63. package/dist/tui/components/IssuePicker.js +64 -0
  64. package/dist/tui/components/RunCompletionSummary.js +6 -3
  65. package/dist/tui/hooks/useAgentOrchestrator.d.ts +29 -0
  66. package/dist/tui/hooks/useAgentOrchestrator.js +453 -0
  67. package/dist/tui/orchestration/interview-orchestrator.d.ts +5 -1
  68. package/dist/tui/orchestration/interview-orchestrator.js +27 -6
  69. package/dist/tui/screens/AgentScreen.d.ts +21 -0
  70. package/dist/tui/screens/AgentScreen.js +159 -0
  71. package/dist/tui/screens/InitScreen.js +4 -0
  72. package/dist/tui/screens/InterviewScreen.d.ts +3 -1
  73. package/dist/tui/screens/InterviewScreen.js +146 -10
  74. package/dist/tui/screens/MainShell.d.ts +1 -1
  75. package/dist/tui/screens/MainShell.js +36 -1
  76. package/dist/tui/screens/RunScreen.js +38 -6
  77. package/dist/tui/utils/build-run-summary.d.ts +1 -1
  78. package/dist/tui/utils/build-run-summary.js +40 -84
  79. package/dist/tui/utils/clear-screen.d.ts +14 -0
  80. package/dist/tui/utils/clear-screen.js +16 -0
  81. package/dist/tui/utils/loop-status.d.ts +41 -1
  82. package/dist/tui/utils/loop-status.js +243 -35
  83. package/dist/tui/utils/pr-summary.d.ts +3 -2
  84. package/dist/tui/utils/pr-summary.js +41 -6
  85. package/dist/utils/config.d.ts +8 -0
  86. package/dist/utils/config.js +8 -0
  87. package/dist/utils/github.d.ts +32 -0
  88. package/dist/utils/github.js +106 -0
  89. package/package.json +4 -1
  90. package/src/templates/prompts/PROMPT.md.tmpl +13 -10
  91. package/src/templates/prompts/PROMPT_e2e.md.tmpl +13 -7
  92. package/src/templates/prompts/PROMPT_feature.md.tmpl +16 -3
  93. package/src/templates/prompts/PROMPT_review_auto.md.tmpl +32 -12
  94. package/src/templates/prompts/PROMPT_review_manual.md.tmpl +4 -1
  95. package/src/templates/prompts/PROMPT_review_merge.md.tmpl +39 -14
  96. package/src/templates/prompts/PROMPT_verify.md.tmpl +5 -2
  97. package/src/templates/scripts/feature-loop.sh.tmpl +441 -69
@@ -0,0 +1,119 @@
1
+ import { tool, zodSchema } from 'ai';
2
+ import { z } from 'zod';
3
+ import { execFile } from 'node:child_process';
4
+ function ghExec(args) {
5
+ return new Promise((resolve, reject) => {
6
+ execFile('gh', args, { timeout: 15000 }, (error, stdout) => {
7
+ if (error)
8
+ reject(error);
9
+ else
10
+ resolve(String(stdout));
11
+ });
12
+ });
13
+ }
14
+ export function createReportingTools(owner, repo) {
15
+ const commentOnIssue = tool({
16
+ description: 'Post a status comment on a GitHub issue.',
17
+ inputSchema: zodSchema(z.object({
18
+ issueNumber: z.number().int().describe('Issue number to comment on'),
19
+ body: z.string().describe('Comment body in markdown'),
20
+ })),
21
+ execute: async ({ issueNumber, body }) => {
22
+ try {
23
+ await ghExec([
24
+ 'issue', 'comment', String(issueNumber),
25
+ '--repo', `${owner}/${repo}`,
26
+ '--body', body,
27
+ ]);
28
+ return { success: true };
29
+ }
30
+ catch (err) {
31
+ return { success: false, error: err instanceof Error ? err.message : String(err) };
32
+ }
33
+ },
34
+ });
35
+ const createIssue = tool({
36
+ description: 'Create a new GitHub issue. Use for tech debt, blockers, bugs, or follow-up work discovered during development.',
37
+ inputSchema: zodSchema(z.object({
38
+ title: z.string().describe('Issue title'),
39
+ body: z.string().describe('Issue body in markdown'),
40
+ labels: z.array(z.string()).default([]).describe('Labels to apply (e.g. bug, P0, tech-debt, blocker)'),
41
+ })),
42
+ execute: async ({ title, body, labels }) => {
43
+ try {
44
+ const args = [
45
+ 'issue', 'create',
46
+ '--repo', `${owner}/${repo}`,
47
+ '--title', title,
48
+ '--body', body,
49
+ ];
50
+ for (const label of labels) {
51
+ args.push('--label', label);
52
+ }
53
+ const stdout = await ghExec(args);
54
+ const url = stdout.trim();
55
+ const match = url.match(/\/(\d+)\s*$/);
56
+ return {
57
+ success: true,
58
+ issueNumber: match ? parseInt(match[1], 10) : undefined,
59
+ url,
60
+ };
61
+ }
62
+ catch (err) {
63
+ return { success: false, error: err instanceof Error ? err.message : String(err) };
64
+ }
65
+ },
66
+ });
67
+ const closeIssue = tool({
68
+ description: 'Close a GitHub issue. Use after verifying the work is actually shipped (PR merged, code on main).',
69
+ inputSchema: zodSchema(z.object({
70
+ issueNumber: z.number().int().describe('Issue number to close'),
71
+ comment: z.string().optional().describe('Optional closing comment'),
72
+ })),
73
+ execute: async ({ issueNumber, comment }) => {
74
+ try {
75
+ const args = [
76
+ 'issue', 'close', String(issueNumber),
77
+ '--repo', `${owner}/${repo}`,
78
+ ];
79
+ if (comment)
80
+ args.push('--comment', comment);
81
+ await ghExec(args);
82
+ return { success: true };
83
+ }
84
+ catch (err) {
85
+ return { success: false, error: err instanceof Error ? err.message : String(err) };
86
+ }
87
+ },
88
+ });
89
+ const checkAllBoxes = tool({
90
+ description: 'Check all acceptance-criteria checkboxes in a GitHub issue body. Use after verifying all criteria are met.',
91
+ inputSchema: zodSchema(z.object({
92
+ issueNumber: z.number().int().describe('Issue number to update'),
93
+ })),
94
+ execute: async ({ issueNumber }) => {
95
+ try {
96
+ const body = await ghExec([
97
+ 'issue', 'view', String(issueNumber),
98
+ '--repo', `${owner}/${repo}`,
99
+ '--json', 'body', '--jq', '.body',
100
+ ]);
101
+ const updated = body.replace(/- \[ \]/g, '- [x]');
102
+ if (updated === body) {
103
+ return { success: true, changed: false, message: 'No unchecked boxes found' };
104
+ }
105
+ await ghExec([
106
+ 'issue', 'edit', String(issueNumber),
107
+ '--repo', `${owner}/${repo}`,
108
+ '--body', updated,
109
+ ]);
110
+ const checked = (updated.match(/- \[x\]/gi) ?? []).length;
111
+ return { success: true, changed: true, totalChecked: checked };
112
+ }
113
+ catch (err) {
114
+ return { success: false, error: err instanceof Error ? err.message : String(err) };
115
+ }
116
+ },
117
+ });
118
+ return { commentOnIssue, createIssue, closeIssue, checkAllBoxes };
119
+ }
@@ -0,0 +1,2 @@
1
+ import { z } from 'zod';
2
+ export declare const FEATURE_NAME_SCHEMA: z.ZodString;
@@ -0,0 +1,3 @@
1
+ import { z } from 'zod';
2
+ export const FEATURE_NAME_SCHEMA = z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/).max(100)
3
+ .describe('Feature name (alphanumeric, hyphens, underscores)');
@@ -0,0 +1,45 @@
1
+ import type { LanguageModel } from 'ai';
2
+ export type ReviewMode = 'manual' | 'auto' | 'merge';
3
+ export interface AgentConfig {
4
+ model: LanguageModel;
5
+ modelId?: string;
6
+ provider?: string;
7
+ projectRoot: string;
8
+ owner: string;
9
+ repo: string;
10
+ maxSteps?: number;
11
+ maxItems?: number;
12
+ labels?: string[];
13
+ reviewMode?: ReviewMode;
14
+ dryRun?: boolean;
15
+ onStepUpdate?: (event: AgentStepEvent) => void;
16
+ onProgress?: (toolName: string, line: string) => void;
17
+ }
18
+ export interface AgentStepEvent {
19
+ toolCalls: Array<{
20
+ toolName: string;
21
+ args: unknown;
22
+ }>;
23
+ toolResults: Array<{
24
+ toolName: string;
25
+ result: unknown;
26
+ }>;
27
+ completedItems: number;
28
+ }
29
+ export interface AgentLogEntry {
30
+ timestamp: string;
31
+ message: string;
32
+ level: 'info' | 'warn' | 'error' | 'success';
33
+ }
34
+ export type AgentPhase = 'idle' | 'planning' | 'generating_spec' | 'running_loop' | 'reporting' | 'reflecting';
35
+ export interface AgentIssueState {
36
+ issueNumber: number;
37
+ title: string;
38
+ labels: string[];
39
+ phase: AgentPhase;
40
+ loopPhase?: string;
41
+ loopFeatureName?: string;
42
+ loopIterations?: number;
43
+ prUrl?: string;
44
+ error?: string;
45
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -150,6 +150,10 @@ Be concise but thorough. Focus on understanding the user's needs before proposin
150
150
  model,
151
151
  messages,
152
152
  ...(isReasoningModel(this.modelId) ? {} : { temperature: 0.7 }),
153
+ experimental_telemetry: {
154
+ isEnabled: true,
155
+ metadata: { agent: 'conversation-manager', provider: this.provider, model: this.modelId },
156
+ },
153
157
  };
154
158
  if (this.tools && Object.keys(this.tools).length > 0) {
155
159
  options.tools = this.tools;
@@ -192,6 +196,10 @@ Be concise but thorough. Focus on understanding the user's needs before proposin
192
196
  model,
193
197
  messages,
194
198
  ...(isReasoningModel(this.modelId) ? {} : { temperature: 0.7 }),
199
+ experimental_telemetry: {
200
+ isEnabled: true,
201
+ metadata: { agent: 'conversation-manager', provider: this.provider, model: this.modelId },
202
+ },
195
203
  });
196
204
  let fullResponse = '';
197
205
  for await (const textPart of result.textStream) {
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { readFileSync, existsSync } from 'node:fs';
6
6
  import { resolve, isAbsolute } from 'node:path';
7
+ import { isGitHubIssueUrl, isGhInstalled, fetchGitHubIssue } from '../../utils/github.js';
7
8
  const MAX_CONTENT_LENGTH = 10000;
8
9
  const FETCH_TIMEOUT = 10000;
9
10
  /**
@@ -127,11 +128,37 @@ function readFromFile(filePath, projectRoot) {
127
128
  };
128
129
  }
129
130
  }
131
+ /**
132
+ * Fetch GitHub issue content via gh CLI
133
+ */
134
+ async function fetchGitHubContent(owner, repo, number) {
135
+ if (!(await isGhInstalled()))
136
+ return null;
137
+ const issue = await fetchGitHubIssue(owner, repo, number);
138
+ if (!issue)
139
+ return null;
140
+ let content = `# ${issue.title}\n\n${issue.body ?? ''}`;
141
+ const truncated = content.length > MAX_CONTENT_LENGTH;
142
+ if (truncated) {
143
+ content = content.slice(0, MAX_CONTENT_LENGTH) + '\n\n[Content truncated...]';
144
+ }
145
+ return {
146
+ source: `GitHub issue #${number}`,
147
+ content,
148
+ truncated,
149
+ };
150
+ }
130
151
  /**
131
152
  * Fetch content from a URL or local file path
132
153
  */
133
154
  export async function fetchContent(input, projectRoot) {
134
155
  if (isUrl(input)) {
156
+ const ghIssue = isGitHubIssueUrl(input);
157
+ if (ghIssue) {
158
+ const result = await fetchGitHubContent(ghIssue.owner, ghIssue.repo, ghIssue.number);
159
+ if (result)
160
+ return result;
161
+ }
135
162
  return fetchFromUrl(input);
136
163
  }
137
164
  return readFromFile(input, projectRoot);
@@ -35,12 +35,12 @@ export const KNOWN_API_KEYS = [
35
35
  export const AVAILABLE_MODELS = {
36
36
  anthropic: [
37
37
  { value: 'claude-opus-4-6', label: 'Claude Opus 4.6', hint: 'most capable' },
38
- { value: 'claude-sonnet-4-5-20250929', label: 'Claude Sonnet 4.5', hint: 'recommended' },
38
+ { value: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6', hint: 'recommended' },
39
39
  { value: 'claude-haiku-4-5-20251001', label: 'Claude Haiku 4.5', hint: 'fastest' },
40
40
  ],
41
41
  openai: [
42
42
  { value: 'gpt-5.2', label: 'GPT-5.2', hint: 'most capable' },
43
- { value: 'gpt-5.2-codex', label: 'GPT-5.2 Codex', hint: 'best for code' },
43
+ { value: 'gpt-5.3-codex', label: 'GPT-5.3 Codex', hint: 'best for code' },
44
44
  { value: 'gpt-5.1', label: 'GPT-5.1', hint: 'previous gen' },
45
45
  { value: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max', hint: 'previous codex' },
46
46
  { value: 'gpt-5-mini', label: 'GPT-5 Mini', hint: 'fastest' },
@@ -60,7 +60,7 @@ export const AVAILABLE_MODELS = {
60
60
  * Using balanced models for good results
61
61
  */
62
62
  const DEFAULT_MODELS = {
63
- anthropic: 'claude-sonnet-4-5-20250929',
63
+ anthropic: 'claude-sonnet-4-6',
64
64
  openai: 'gpt-5.2',
65
65
  openrouter: 'google/gemini-3-pro-preview',
66
66
  };
@@ -68,7 +68,7 @@ const DEFAULT_MODELS = {
68
68
  * Anthropic shorthand aliases for legacy configs
69
69
  */
70
70
  const ANTHROPIC_MODEL_ALIASES = {
71
- sonnet: 'claude-sonnet-4-5-20250929',
71
+ sonnet: 'claude-sonnet-4-6',
72
72
  opus: 'claude-opus-4-6',
73
73
  haiku: 'claude-haiku-4-5-20251001',
74
74
  };
@@ -177,7 +177,7 @@ const REASONING_MODELS = [
177
177
  'o3', 'o3-mini',
178
178
  'gpt-5', 'gpt-5.1', 'gpt-5-mini',
179
179
  'gpt-5.1-codex', 'gpt-5.1-codex-max',
180
- 'gpt-5.2', 'gpt-5.2-codex',
180
+ 'gpt-5.2', 'gpt-5.3-codex',
181
181
  ];
182
182
  /**
183
183
  * Check if a model is a reasoning model that doesn't support temperature
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Agent Command — Headless autonomous backlog executor
3
+ *
4
+ * Reads provider and model from ralph.config.cjs (set during wiggum init),
5
+ * detects GitHub remote, creates the agent orchestrator, and runs it
6
+ * in headless mode (generate or stream).
7
+ */
8
+ export interface AgentOptions {
9
+ model?: string;
10
+ maxItems?: number;
11
+ maxSteps?: number;
12
+ labels?: string[];
13
+ reviewMode?: 'manual' | 'auto' | 'merge';
14
+ dryRun?: boolean;
15
+ stream?: boolean;
16
+ }
17
+ export declare function agentCommand(options?: AgentOptions): Promise<void>;
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Agent Command — Headless autonomous backlog executor
3
+ *
4
+ * Reads provider and model from ralph.config.cjs (set during wiggum init),
5
+ * detects GitHub remote, creates the agent orchestrator, and runs it
6
+ * in headless mode (generate or stream).
7
+ */
8
+ import { logger } from '../utils/logger.js';
9
+ import { createAgentOrchestrator, } from '../agent/orchestrator.js';
10
+ import { resolveAgentEnv } from '../agent/resolve-config.js';
11
+ import { initTracing, flushTracing, traced, currentSpan } from '../utils/tracing.js';
12
+ export async function agentCommand(options = {}) {
13
+ const projectRoot = process.cwd();
14
+ // Initialize Braintrust tracing (no-op if BRAINTRUST_API_KEY not set)
15
+ initTracing();
16
+ // Resolve provider, model, and GitHub remote
17
+ let env;
18
+ try {
19
+ env = await resolveAgentEnv(projectRoot, { model: options.model });
20
+ }
21
+ catch (err) {
22
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
23
+ process.exit(1);
24
+ }
25
+ const { provider, model, modelId, owner, repo } = env;
26
+ // 4. Create orchestrator
27
+ const agentConfig = {
28
+ model,
29
+ modelId: modelId ?? undefined,
30
+ provider,
31
+ projectRoot,
32
+ owner,
33
+ repo,
34
+ maxSteps: options.maxSteps,
35
+ maxItems: options.maxItems,
36
+ labels: options.labels,
37
+ reviewMode: options.reviewMode,
38
+ dryRun: options.dryRun,
39
+ onStepUpdate: (event) => {
40
+ const log = options.stream
41
+ ? (msg) => process.stdout.write(`${msg}\n`)
42
+ : (msg) => logger.info(msg);
43
+ for (const tc of event.toolCalls) {
44
+ log(`[tool] ${tc.toolName}`);
45
+ }
46
+ for (const tr of event.toolResults) {
47
+ const summary = typeof tr.result === 'object' && tr.result !== null
48
+ ? tr.result.status ?? tr.result.success ?? 'done'
49
+ : 'done';
50
+ log(`[tool:done] ${tr.toolName} → ${summary}`);
51
+ }
52
+ },
53
+ onProgress: (toolName, line) => {
54
+ const log = options.stream
55
+ ? (msg) => process.stdout.write(`${msg}\n`)
56
+ : (msg) => logger.info(msg);
57
+ log(` [${toolName}] ${line}`);
58
+ },
59
+ };
60
+ const agent = createAgentOrchestrator(agentConfig);
61
+ // 5. Run in headless mode
62
+ logger.info(`Agent starting: ${owner}/${repo} with ${provider}/${modelId ?? 'default'}`);
63
+ try {
64
+ await traced(async () => {
65
+ currentSpan().log({
66
+ input: {
67
+ owner,
68
+ repo,
69
+ provider,
70
+ model: modelId ?? 'default',
71
+ maxItems: options.maxItems,
72
+ maxSteps: options.maxSteps,
73
+ labels: options.labels,
74
+ dryRun: options.dryRun ?? false,
75
+ stream: options.stream ?? false,
76
+ },
77
+ metadata: {
78
+ command: 'agent',
79
+ owner,
80
+ repo,
81
+ provider,
82
+ model: modelId ?? 'default',
83
+ dryRun: String(options.dryRun ?? false),
84
+ },
85
+ tags: ['agent'],
86
+ });
87
+ if (options.stream) {
88
+ const result = await agent.stream({ prompt: 'Begin working through the backlog.' });
89
+ let hasOutput = false;
90
+ for await (const chunk of result.textStream) {
91
+ process.stdout.write(chunk);
92
+ hasOutput = true;
93
+ }
94
+ if (hasOutput) {
95
+ process.stdout.write('\n');
96
+ }
97
+ }
98
+ else {
99
+ const result = await agent.generate({ prompt: 'Begin working through the backlog.' });
100
+ if (result.text) {
101
+ console.log(result.text);
102
+ }
103
+ }
104
+ }, { name: 'agent-run' });
105
+ }
106
+ catch (err) {
107
+ const message = err instanceof Error ? err.message : String(err);
108
+ console.error(`Error: Agent failed — ${message}`);
109
+ process.exit(1);
110
+ }
111
+ finally {
112
+ await flushTracing();
113
+ }
114
+ }