@avantmedia/af 0.0.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.
- package/LICENSE +21 -0
- package/README.md +539 -0
- package/af +2 -0
- package/bun-upgrade.ts +130 -0
- package/commands/bun.ts +55 -0
- package/commands/changes.ts +35 -0
- package/commands/e2e.ts +12 -0
- package/commands/help.ts +236 -0
- package/commands/install-extension.ts +133 -0
- package/commands/jira.ts +577 -0
- package/commands/licenses.ts +32 -0
- package/commands/npm.ts +55 -0
- package/commands/scaffold.ts +105 -0
- package/commands/setup.tsx +156 -0
- package/commands/spec.ts +405 -0
- package/commands/stop-hook.ts +90 -0
- package/commands/todo.ts +208 -0
- package/commands/versions.ts +150 -0
- package/commands/watch.ts +344 -0
- package/commands/worktree.ts +424 -0
- package/components/change-select.tsx +71 -0
- package/components/confirm.tsx +41 -0
- package/components/file-conflict.tsx +52 -0
- package/components/input.tsx +53 -0
- package/components/layout.tsx +70 -0
- package/components/messages.tsx +48 -0
- package/components/progress.tsx +71 -0
- package/components/select.tsx +90 -0
- package/components/status-display.tsx +74 -0
- package/components/table.tsx +79 -0
- package/generated/setup-manifest.ts +67 -0
- package/git-worktree.ts +184 -0
- package/main.ts +12 -0
- package/npm-upgrade.ts +117 -0
- package/package.json +83 -0
- package/resources/copy-prompt-reporter.ts +443 -0
- package/router.ts +220 -0
- package/setup/.claude/commands/commit-work.md +47 -0
- package/setup/.claude/commands/complete-work.md +34 -0
- package/setup/.claude/commands/e2e.md +29 -0
- package/setup/.claude/commands/start-work.md +51 -0
- package/setup/.claude/skills/pm/SKILL.md +294 -0
- package/setup/.claude/skills/pm/templates/api-endpoint.md +69 -0
- package/setup/.claude/skills/pm/templates/bug-fix.md +77 -0
- package/setup/.claude/skills/pm/templates/feature.md +87 -0
- package/setup/.claude/skills/pm/templates/ui-component.md +78 -0
- package/utils/change-select-render.tsx +44 -0
- package/utils/claude.ts +9 -0
- package/utils/config.ts +58 -0
- package/utils/env.ts +53 -0
- package/utils/git.ts +120 -0
- package/utils/ink-render.tsx +50 -0
- package/utils/openspec.ts +54 -0
- package/utils/output.ts +104 -0
- package/utils/proposal.ts +160 -0
- package/utils/resources.ts +64 -0
- package/utils/setup-files.ts +230 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# API Endpoint Template
|
|
2
|
+
|
|
3
|
+
Work breakdown for implementing a new REST or GraphQL API endpoint.
|
|
4
|
+
|
|
5
|
+
## Task Structure
|
|
6
|
+
|
|
7
|
+
When breaking down an API endpoint epic, create these subtasks:
|
|
8
|
+
|
|
9
|
+
### 1. Design & Planning
|
|
10
|
+
- **Summary**: Design {endpoint} API contract
|
|
11
|
+
- **Description**: Define request/response schemas, error codes, and edge cases
|
|
12
|
+
- **Estimate**: 1-2h
|
|
13
|
+
|
|
14
|
+
### 2. Database/Entity Changes (if needed)
|
|
15
|
+
- **Summary**: Add database entities for {endpoint}
|
|
16
|
+
- **Description**: Create or modify TypeORM entities, generate migration
|
|
17
|
+
- **Estimate**: 1-2h
|
|
18
|
+
|
|
19
|
+
### 3. Controller Implementation
|
|
20
|
+
- **Summary**: Implement {endpoint} controller
|
|
21
|
+
- **Description**: Create endpoint with request validation, business logic, response formatting
|
|
22
|
+
- **Estimate**: 2-4h
|
|
23
|
+
|
|
24
|
+
### 4. Input Validation
|
|
25
|
+
- **Summary**: Add validation for {endpoint} inputs
|
|
26
|
+
- **Description**: Implement request validation with proper error messages
|
|
27
|
+
- **Estimate**: 1h
|
|
28
|
+
|
|
29
|
+
### 5. Error Handling
|
|
30
|
+
- **Summary**: Add error handling for {endpoint}
|
|
31
|
+
- **Description**: Handle edge cases, return appropriate error codes
|
|
32
|
+
- **Estimate**: 1h
|
|
33
|
+
|
|
34
|
+
### 6. Unit Tests
|
|
35
|
+
- **Summary**: Write unit tests for {endpoint}
|
|
36
|
+
- **Description**: Test controller logic, validation, error cases
|
|
37
|
+
- **Estimate**: 2-3h
|
|
38
|
+
|
|
39
|
+
### 7. Integration Tests (optional)
|
|
40
|
+
- **Summary**: Write integration tests for {endpoint}
|
|
41
|
+
- **Description**: Test full request/response cycle with database
|
|
42
|
+
- **Estimate**: 1-2h
|
|
43
|
+
|
|
44
|
+
### 8. Documentation (optional)
|
|
45
|
+
- **Summary**: Document {endpoint} API
|
|
46
|
+
- **Description**: Add OpenAPI/Swagger docs or update API documentation
|
|
47
|
+
- **Estimate**: 30m
|
|
48
|
+
|
|
49
|
+
## Example Jira Commands
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Create subtasks for PROJ-100 (the epic)
|
|
53
|
+
af jira create --project PROJ --type Sub-task --summary "Design user preferences API contract" --parent PROJ-100
|
|
54
|
+
|
|
55
|
+
af jira create --project PROJ --type Sub-task --summary "Implement user preferences controller" --parent PROJ-100
|
|
56
|
+
|
|
57
|
+
af jira create --project PROJ --type Sub-task --summary "Add validation for user preferences inputs" --parent PROJ-100
|
|
58
|
+
|
|
59
|
+
af jira create --project PROJ --type Sub-task --summary "Write unit tests for user preferences endpoint" --parent PROJ-100
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Checklist
|
|
63
|
+
|
|
64
|
+
Before marking the epic as done:
|
|
65
|
+
- [ ] Endpoint accessible and returns expected responses
|
|
66
|
+
- [ ] Input validation working with clear error messages
|
|
67
|
+
- [ ] Error cases handled gracefully
|
|
68
|
+
- [ ] Unit tests passing
|
|
69
|
+
- [ ] No security vulnerabilities (auth, injection, etc.)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Bug Fix Template
|
|
2
|
+
|
|
3
|
+
Work breakdown for investigating and fixing a bug.
|
|
4
|
+
|
|
5
|
+
## Task Structure
|
|
6
|
+
|
|
7
|
+
When breaking down a bug fix, create these subtasks:
|
|
8
|
+
|
|
9
|
+
### 1. Reproduce
|
|
10
|
+
- **Summary**: Reproduce {bug}
|
|
11
|
+
- **Description**: Confirm reproduction steps, document environment, capture evidence
|
|
12
|
+
- **Estimate**: 30m-1h
|
|
13
|
+
|
|
14
|
+
### 2. Investigate
|
|
15
|
+
- **Summary**: Investigate root cause of {bug}
|
|
16
|
+
- **Description**: Debug, trace code paths, identify the source of the issue
|
|
17
|
+
- **Estimate**: 1-3h
|
|
18
|
+
|
|
19
|
+
### 3. Implement Fix
|
|
20
|
+
- **Summary**: Fix {bug}
|
|
21
|
+
- **Description**: Implement the solution, handle edge cases
|
|
22
|
+
- **Estimate**: 1-3h
|
|
23
|
+
|
|
24
|
+
### 4. Write Regression Test
|
|
25
|
+
- **Summary**: Add regression test for {bug}
|
|
26
|
+
- **Description**: Write test that would have caught this bug
|
|
27
|
+
- **Estimate**: 30m-1h
|
|
28
|
+
|
|
29
|
+
### 5. Verify Fix
|
|
30
|
+
- **Summary**: Verify {bug} is fixed
|
|
31
|
+
- **Description**: Confirm original reproduction steps no longer reproduce the issue
|
|
32
|
+
- **Estimate**: 30m
|
|
33
|
+
|
|
34
|
+
## Simplified Flow (Minor Bugs)
|
|
35
|
+
|
|
36
|
+
For simple bugs, combine into fewer tasks:
|
|
37
|
+
|
|
38
|
+
### 1. Investigate & Fix
|
|
39
|
+
- **Summary**: Investigate and fix {bug}
|
|
40
|
+
- **Description**: Reproduce, find root cause, implement fix
|
|
41
|
+
- **Estimate**: 1-2h
|
|
42
|
+
|
|
43
|
+
### 2. Add Test
|
|
44
|
+
- **Summary**: Add regression test for {bug}
|
|
45
|
+
- **Description**: Write test to prevent recurrence
|
|
46
|
+
- **Estimate**: 30m
|
|
47
|
+
|
|
48
|
+
## Example Jira Commands
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Create subtasks for PROJ-300 (the bug)
|
|
52
|
+
af jira create --project PROJ --type Sub-task --summary "Reproduce login timeout bug" --parent PROJ-300
|
|
53
|
+
|
|
54
|
+
af jira create --project PROJ --type Sub-task --summary "Investigate root cause of login timeout" --parent PROJ-300
|
|
55
|
+
|
|
56
|
+
af jira create --project PROJ --type Sub-task --summary "Fix login timeout bug" --parent PROJ-300
|
|
57
|
+
|
|
58
|
+
af jira create --project PROJ --type Sub-task --summary "Add regression test for login timeout" --parent PROJ-300
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Investigation Checklist
|
|
62
|
+
|
|
63
|
+
When investigating:
|
|
64
|
+
- [ ] Can reproduce consistently?
|
|
65
|
+
- [ ] What changed recently? (git log, deployments)
|
|
66
|
+
- [ ] Environment-specific? (browser, OS, data)
|
|
67
|
+
- [ ] Related to recent code changes?
|
|
68
|
+
- [ ] Are there similar past bugs?
|
|
69
|
+
|
|
70
|
+
## Fix Checklist
|
|
71
|
+
|
|
72
|
+
Before marking as done:
|
|
73
|
+
- [ ] Bug no longer reproducible
|
|
74
|
+
- [ ] Regression test added
|
|
75
|
+
- [ ] No new issues introduced
|
|
76
|
+
- [ ] Related areas tested
|
|
77
|
+
- [ ] Root cause documented in ticket
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Feature Template
|
|
2
|
+
|
|
3
|
+
Work breakdown for implementing a general feature.
|
|
4
|
+
|
|
5
|
+
## Task Structure
|
|
6
|
+
|
|
7
|
+
When breaking down a feature epic, create these subtasks:
|
|
8
|
+
|
|
9
|
+
### 1. Requirements Analysis
|
|
10
|
+
- **Summary**: Analyze requirements for {feature}
|
|
11
|
+
- **Description**: Review specs, identify edge cases, clarify with stakeholders
|
|
12
|
+
- **Estimate**: 1-2h
|
|
13
|
+
|
|
14
|
+
### 2. Technical Design
|
|
15
|
+
- **Summary**: Design implementation approach for {feature}
|
|
16
|
+
- **Description**: Plan architecture, data models, API changes, UI components
|
|
17
|
+
- **Estimate**: 1-3h
|
|
18
|
+
|
|
19
|
+
### 3. Backend Implementation
|
|
20
|
+
- **Summary**: Implement backend for {feature}
|
|
21
|
+
- **Description**: Database changes, API endpoints, business logic
|
|
22
|
+
- **Estimate**: 2-8h (varies by complexity)
|
|
23
|
+
|
|
24
|
+
### 4. Frontend Implementation
|
|
25
|
+
- **Summary**: Implement frontend for {feature}
|
|
26
|
+
- **Description**: UI components, state management, API integration
|
|
27
|
+
- **Estimate**: 2-8h (varies by complexity)
|
|
28
|
+
|
|
29
|
+
### 5. Integration
|
|
30
|
+
- **Summary**: Integrate {feature} frontend and backend
|
|
31
|
+
- **Description**: Connect UI to API, handle loading/error states
|
|
32
|
+
- **Estimate**: 1-2h
|
|
33
|
+
|
|
34
|
+
### 6. Testing
|
|
35
|
+
- **Summary**: Test {feature}
|
|
36
|
+
- **Description**: Unit tests, integration tests, E2E tests
|
|
37
|
+
- **Estimate**: 2-4h
|
|
38
|
+
|
|
39
|
+
### 7. Documentation
|
|
40
|
+
- **Summary**: Document {feature}
|
|
41
|
+
- **Description**: Update user docs, API docs, or internal documentation
|
|
42
|
+
- **Estimate**: 1h
|
|
43
|
+
|
|
44
|
+
## Scaling for Complexity
|
|
45
|
+
|
|
46
|
+
**Small Feature** (1-2 days):
|
|
47
|
+
- Combine analysis + design
|
|
48
|
+
- Single implementation task
|
|
49
|
+
- Basic testing
|
|
50
|
+
|
|
51
|
+
**Medium Feature** (3-5 days):
|
|
52
|
+
- Separate analysis and design
|
|
53
|
+
- Split backend/frontend
|
|
54
|
+
- Comprehensive testing
|
|
55
|
+
|
|
56
|
+
**Large Feature** (1+ weeks):
|
|
57
|
+
- Detailed requirements analysis
|
|
58
|
+
- Technical design document
|
|
59
|
+
- Multiple implementation phases
|
|
60
|
+
- Phased testing (unit -> integration -> E2E)
|
|
61
|
+
- Documentation and training
|
|
62
|
+
|
|
63
|
+
## Example Jira Commands
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# Create subtasks for PROJ-400 (the epic)
|
|
67
|
+
af jira create --project PROJ --type Sub-task --summary "Analyze requirements for email notifications" --parent PROJ-400
|
|
68
|
+
|
|
69
|
+
af jira create --project PROJ --type Sub-task --summary "Design email notification system" --parent PROJ-400
|
|
70
|
+
|
|
71
|
+
af jira create --project PROJ --type Sub-task --summary "Implement email notification backend" --parent PROJ-400
|
|
72
|
+
|
|
73
|
+
af jira create --project PROJ --type Sub-task --summary "Implement email notification UI" --parent PROJ-400
|
|
74
|
+
|
|
75
|
+
af jira create --project PROJ --type Sub-task --summary "Write tests for email notifications" --parent PROJ-400
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Definition of Done
|
|
79
|
+
|
|
80
|
+
Before marking the epic as done:
|
|
81
|
+
- [ ] All acceptance criteria met
|
|
82
|
+
- [ ] Code reviewed and approved
|
|
83
|
+
- [ ] Tests passing (unit, integration, E2E as appropriate)
|
|
84
|
+
- [ ] No known bugs or regressions
|
|
85
|
+
- [ ] Documentation updated
|
|
86
|
+
- [ ] Deployed to staging and verified
|
|
87
|
+
- [ ] Product owner sign-off (if applicable)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# UI Component Template
|
|
2
|
+
|
|
3
|
+
Work breakdown for implementing a new React UI component or page.
|
|
4
|
+
|
|
5
|
+
## Task Structure
|
|
6
|
+
|
|
7
|
+
When breaking down a UI component epic, create these subtasks:
|
|
8
|
+
|
|
9
|
+
### 1. Design Review
|
|
10
|
+
- **Summary**: Review design for {component}
|
|
11
|
+
- **Description**: Analyze mockups/designs, identify edge cases, confirm responsive requirements
|
|
12
|
+
- **Estimate**: 30m-1h
|
|
13
|
+
|
|
14
|
+
### 2. Component Structure
|
|
15
|
+
- **Summary**: Create {component} component structure
|
|
16
|
+
- **Description**: Set up component files, props interface, basic layout
|
|
17
|
+
- **Estimate**: 1-2h
|
|
18
|
+
|
|
19
|
+
### 3. Styling
|
|
20
|
+
- **Summary**: Implement {component} styling
|
|
21
|
+
- **Description**: Apply design system styles, handle responsive breakpoints
|
|
22
|
+
- **Estimate**: 1-2h
|
|
23
|
+
|
|
24
|
+
### 4. State Management
|
|
25
|
+
- **Summary**: Add state management for {component}
|
|
26
|
+
- **Description**: Implement local state, connect to global state if needed
|
|
27
|
+
- **Estimate**: 1-2h
|
|
28
|
+
|
|
29
|
+
### 5. API Integration
|
|
30
|
+
- **Summary**: Connect {component} to API
|
|
31
|
+
- **Description**: Implement data fetching, loading states, error handling
|
|
32
|
+
- **Estimate**: 1-2h
|
|
33
|
+
|
|
34
|
+
### 6. User Interactions
|
|
35
|
+
- **Summary**: Implement {component} interactions
|
|
36
|
+
- **Description**: Add event handlers, form validation, user feedback
|
|
37
|
+
- **Estimate**: 1-2h
|
|
38
|
+
|
|
39
|
+
### 7. Accessibility
|
|
40
|
+
- **Summary**: Add accessibility to {component}
|
|
41
|
+
- **Description**: ARIA labels, keyboard navigation, screen reader support
|
|
42
|
+
- **Estimate**: 1h
|
|
43
|
+
|
|
44
|
+
### 8. Unit Tests
|
|
45
|
+
- **Summary**: Write tests for {component}
|
|
46
|
+
- **Description**: Test component rendering, interactions, edge cases
|
|
47
|
+
- **Estimate**: 1-2h
|
|
48
|
+
|
|
49
|
+
### 9. E2E Tests
|
|
50
|
+
- **Summary**: Add E2E test for {component}
|
|
51
|
+
- **Description**: Test user flow with Playwright
|
|
52
|
+
- **Estimate**: 1h
|
|
53
|
+
|
|
54
|
+
## Example Jira Commands
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Create subtasks for PROJ-200 (the epic)
|
|
58
|
+
af jira create --project PROJ --type Sub-task --summary "Create BookingCalendar component structure" --parent PROJ-200
|
|
59
|
+
|
|
60
|
+
af jira create --project PROJ --type Sub-task --summary "Implement BookingCalendar styling" --parent PROJ-200
|
|
61
|
+
|
|
62
|
+
af jira create --project PROJ --type Sub-task --summary "Connect BookingCalendar to booking API" --parent PROJ-200
|
|
63
|
+
|
|
64
|
+
af jira create --project PROJ --type Sub-task --summary "Write tests for BookingCalendar" --parent PROJ-200
|
|
65
|
+
|
|
66
|
+
af jira create --project PROJ --type Sub-task --summary "Add E2E test for BookingCalendar flow" --parent PROJ-200
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Checklist
|
|
70
|
+
|
|
71
|
+
Before marking the epic as done:
|
|
72
|
+
- [ ] Component matches design mockups
|
|
73
|
+
- [ ] Responsive on all target breakpoints
|
|
74
|
+
- [ ] Loading and error states implemented
|
|
75
|
+
- [ ] Keyboard accessible
|
|
76
|
+
- [ ] Unit tests passing
|
|
77
|
+
- [ ] E2E test covering main flow
|
|
78
|
+
- [ ] No console errors or warnings
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ChangeSelect } from '../components/change-select.tsx';
|
|
3
|
+
import { render } from './ink-render.tsx';
|
|
4
|
+
import type { OngoingChange } from './openspec.ts';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Render the interactive change selection UI and return the selected change ID.
|
|
8
|
+
*
|
|
9
|
+
* @param changes - Array of ongoing changes to choose from
|
|
10
|
+
* @param prompt - Custom prompt text to display (default: "Select a change:")
|
|
11
|
+
* @returns Promise that resolves to selected change ID, or null if cancelled
|
|
12
|
+
*/
|
|
13
|
+
export function renderChangeSelect(
|
|
14
|
+
changes: OngoingChange[],
|
|
15
|
+
prompt?: string,
|
|
16
|
+
): Promise<string | null> {
|
|
17
|
+
return new Promise(resolve => {
|
|
18
|
+
let cancelled = false;
|
|
19
|
+
|
|
20
|
+
const { unmount } = render(
|
|
21
|
+
<ChangeSelect
|
|
22
|
+
changes={changes}
|
|
23
|
+
prompt={prompt}
|
|
24
|
+
onSelect={selectedId => {
|
|
25
|
+
unmount();
|
|
26
|
+
resolve(selectedId);
|
|
27
|
+
}}
|
|
28
|
+
onCancel={() => {
|
|
29
|
+
cancelled = true;
|
|
30
|
+
unmount();
|
|
31
|
+
resolve(null);
|
|
32
|
+
}}
|
|
33
|
+
/>,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
// Handle case where component is unmounted without selection
|
|
37
|
+
process.on('SIGINT', () => {
|
|
38
|
+
if (!cancelled) {
|
|
39
|
+
unmount();
|
|
40
|
+
resolve(null);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|
package/utils/claude.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gets the configured agent command name from the ARTIFEX_AGENT environment variable.
|
|
3
|
+
* Falls back to 'claude' if not set.
|
|
4
|
+
*
|
|
5
|
+
* @returns The agent command name to use
|
|
6
|
+
*/
|
|
7
|
+
export function getAgentCommand(): string {
|
|
8
|
+
return process.env.ARTIFEX_AGENT || 'claude';
|
|
9
|
+
}
|
package/utils/config.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Configuration for the stop-hook command.
|
|
6
|
+
*/
|
|
7
|
+
export interface StopHookConfig {
|
|
8
|
+
ignoredPaths: string[];
|
|
9
|
+
command: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Full af.json configuration structure.
|
|
14
|
+
*/
|
|
15
|
+
export interface AfConfig {
|
|
16
|
+
stopHook?: Partial<StopHookConfig>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Default configuration values.
|
|
21
|
+
*/
|
|
22
|
+
const DEFAULT_STOP_HOOK_CONFIG: StopHookConfig = {
|
|
23
|
+
ignoredPaths: ['openspec/'],
|
|
24
|
+
command: 'af e2e',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Load configuration from af.json in the current working directory.
|
|
29
|
+
* Returns undefined if the file doesn't exist.
|
|
30
|
+
* Throws if the file exists but is invalid JSON.
|
|
31
|
+
*/
|
|
32
|
+
export function loadAfConfig(): AfConfig | undefined {
|
|
33
|
+
const configPath = join(process.cwd(), 'af.json');
|
|
34
|
+
|
|
35
|
+
if (!existsSync(configPath)) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const content = readFileSync(configPath, 'utf-8');
|
|
40
|
+
return JSON.parse(content) as AfConfig;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get the stop-hook configuration, merging af.json settings with defaults.
|
|
45
|
+
* If ignoredPaths is specified in af.json, it completely replaces the defaults.
|
|
46
|
+
*/
|
|
47
|
+
export function getStopHookConfig(): StopHookConfig {
|
|
48
|
+
const afConfig = loadAfConfig();
|
|
49
|
+
|
|
50
|
+
if (!afConfig?.stopHook) {
|
|
51
|
+
return DEFAULT_STOP_HOOK_CONFIG;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
ignoredPaths: afConfig.stopHook.ignoredPaths ?? DEFAULT_STOP_HOOK_CONFIG.ignoredPaths,
|
|
56
|
+
command: afConfig.stopHook.command ?? DEFAULT_STOP_HOOK_CONFIG.command,
|
|
57
|
+
};
|
|
58
|
+
}
|
package/utils/env.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Load environment variables from a .env file in the current working directory.
|
|
6
|
+
* Silently skips if the file doesn't exist - never fails.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Parses KEY=value pairs
|
|
10
|
+
* - Handles quoted values (single and double quotes)
|
|
11
|
+
* - Ignores comments (lines starting with #)
|
|
12
|
+
* - Ignores empty lines
|
|
13
|
+
* - Does not overwrite existing environment variables
|
|
14
|
+
*/
|
|
15
|
+
export function loadEnv(): void {
|
|
16
|
+
const envPath = join(process.cwd(), '.env');
|
|
17
|
+
|
|
18
|
+
if (!existsSync(envPath)) {
|
|
19
|
+
return; // Silently skip if .env doesn't exist
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const content = readFileSync(envPath, 'utf-8');
|
|
23
|
+
|
|
24
|
+
for (const line of content.split('\n')) {
|
|
25
|
+
const trimmed = line.trim();
|
|
26
|
+
|
|
27
|
+
// Skip empty lines and comments
|
|
28
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const eqIndex = trimmed.indexOf('=');
|
|
33
|
+
if (eqIndex === -1) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
38
|
+
let value = trimmed.slice(eqIndex + 1).trim();
|
|
39
|
+
|
|
40
|
+
// Remove surrounding quotes if present
|
|
41
|
+
if (
|
|
42
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
43
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
44
|
+
) {
|
|
45
|
+
value = value.slice(1, -1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Don't overwrite existing environment variables
|
|
49
|
+
if (process.env[key] === undefined) {
|
|
50
|
+
process.env[key] = value;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
package/utils/git.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stage files in a specific directory for git commit.
|
|
5
|
+
*
|
|
6
|
+
* @param directory - The directory path to stage (relative to repo root)
|
|
7
|
+
* @returns true if staging succeeds, false otherwise
|
|
8
|
+
*/
|
|
9
|
+
export function stageDirectory(directory: string): boolean {
|
|
10
|
+
try {
|
|
11
|
+
console.log(`git add "${directory}"`);
|
|
12
|
+
execSync(`git add "${directory}"`, { stdio: 'pipe' });
|
|
13
|
+
return true;
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create a git commit with the specified message.
|
|
21
|
+
*
|
|
22
|
+
* @param message - The commit message
|
|
23
|
+
* @returns true if commit succeeds, false otherwise
|
|
24
|
+
*/
|
|
25
|
+
export function createCommit(message: string): boolean {
|
|
26
|
+
try {
|
|
27
|
+
execSync(`git commit -m "${message}"`, { stdio: 'pipe' });
|
|
28
|
+
return true;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Stage files and create a commit.
|
|
36
|
+
*
|
|
37
|
+
* @param directory - The directory to stage
|
|
38
|
+
* @param message - The commit message
|
|
39
|
+
* @returns Object with success status and optional error message
|
|
40
|
+
*/
|
|
41
|
+
export function stageAndCommit(
|
|
42
|
+
directory: string,
|
|
43
|
+
message: string,
|
|
44
|
+
): { success: boolean; error?: string } {
|
|
45
|
+
if (!stageDirectory(directory)) {
|
|
46
|
+
return { success: false, error: 'Failed to stage files' };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!createCommit(message)) {
|
|
50
|
+
return { success: false, error: 'Failed to create commit' };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { success: true };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Stage all changes (tracked and untracked) and create a commit.
|
|
58
|
+
*
|
|
59
|
+
* @param message - The commit message
|
|
60
|
+
* @returns Object with success status and optional error message
|
|
61
|
+
*/
|
|
62
|
+
export function stageAllAndCommit(message: string): { success: boolean; error?: string } {
|
|
63
|
+
try {
|
|
64
|
+
execSync('git add -A', { stdio: 'pipe' });
|
|
65
|
+
} catch {
|
|
66
|
+
return { success: false, error: 'Failed to stage files' };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!createCommit(message)) {
|
|
70
|
+
return { success: false, error: 'Failed to create commit' };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return { success: true };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Check if there are any changes to commit.
|
|
78
|
+
*
|
|
79
|
+
* @returns true if there are staged or unstaged changes, false otherwise
|
|
80
|
+
*/
|
|
81
|
+
export function hasChangesToCommit(): boolean {
|
|
82
|
+
try {
|
|
83
|
+
const status = execSync('git status --porcelain', { stdio: 'pipe' }).toString();
|
|
84
|
+
return status.trim().length > 0;
|
|
85
|
+
} catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Stage all changes and create a commit with optional trailers.
|
|
92
|
+
*
|
|
93
|
+
* @param message - The commit message
|
|
94
|
+
* @param trailers - Array of trailer objects with key and value
|
|
95
|
+
* @returns Object with success status and optional error message
|
|
96
|
+
*/
|
|
97
|
+
export function stageAllAndCommitWithTrailers(
|
|
98
|
+
message: string,
|
|
99
|
+
trailers: Array<{ key: string; value: string }> = [],
|
|
100
|
+
): { success: boolean; error?: string } {
|
|
101
|
+
try {
|
|
102
|
+
execSync('git add .', { stdio: 'pipe' });
|
|
103
|
+
} catch {
|
|
104
|
+
return { success: false, error: 'Failed to stage files' };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Build commit command with trailers
|
|
108
|
+
const trailerArgs = trailers.map(t => `--trailer "${t.key}: ${t.value}"`).join(' ');
|
|
109
|
+
const commitCmd = trailerArgs
|
|
110
|
+
? `git commit -m "${message}" ${trailerArgs}`
|
|
111
|
+
: `git commit -m "${message}"`;
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
execSync(commitCmd, { stdio: 'pipe' });
|
|
115
|
+
} catch {
|
|
116
|
+
return { success: false, error: 'Failed to create commit' };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return { success: true };
|
|
120
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { render as inkRender } from 'ink';
|
|
2
|
+
import type { ReactElement } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Render an Ink component to the terminal with signal handling
|
|
6
|
+
* @param element The React element to render
|
|
7
|
+
* @returns Object with cleanup function and promise that resolves when component unmounts
|
|
8
|
+
*/
|
|
9
|
+
export function render(element: ReactElement) {
|
|
10
|
+
const { unmount, waitUntilExit, clear } = inkRender(element);
|
|
11
|
+
|
|
12
|
+
// Handle graceful shutdown on SIGINT (Ctrl+C)
|
|
13
|
+
const handleSigInt = () => {
|
|
14
|
+
unmount();
|
|
15
|
+
process.exit(0);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Handle graceful shutdown on SIGTERM
|
|
19
|
+
const handleSigTerm = () => {
|
|
20
|
+
unmount();
|
|
21
|
+
process.exit(0);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
process.on('SIGINT', handleSigInt);
|
|
25
|
+
process.on('SIGTERM', handleSigTerm);
|
|
26
|
+
|
|
27
|
+
// Clean up signal handlers when component unmounts
|
|
28
|
+
const cleanup = () => {
|
|
29
|
+
process.off('SIGINT', handleSigInt);
|
|
30
|
+
process.off('SIGTERM', handleSigTerm);
|
|
31
|
+
unmount();
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
unmount,
|
|
36
|
+
cleanup,
|
|
37
|
+
waitUntilExit,
|
|
38
|
+
clear,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Render an Ink component synchronously and wait for it to finish
|
|
44
|
+
* Useful for fire-and-forget static content
|
|
45
|
+
* @param element The React element to render
|
|
46
|
+
*/
|
|
47
|
+
export function renderSync(element: ReactElement): void {
|
|
48
|
+
const { waitUntilExit } = render(element);
|
|
49
|
+
waitUntilExit();
|
|
50
|
+
}
|