pi-cicd 0.3.0 → 1.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/CHANGELOG.md +19 -0
- package/README.md +34 -40
- package/docs/API.md +61 -0
- package/docs/COMMANDS.md +138 -0
- package/docs/CONFIG.md +123 -0
- package/docs/GUIDE.md +171 -0
- package/docs/PATTERNS.md +49 -0
- package/docs/QUICKSTART.md +99 -0
- package/{dist/index.d.ts → index.ts} +26 -4
- package/install.mjs +34 -0
- package/package.json +21 -21
- package/skills/intelligent-deploy/SKILL.md +229 -0
- package/src/ci/pipeline.ts +130 -0
- package/src/ci/pr-creator.ts +74 -0
- package/src/ci/report.ts +65 -0
- package/src/ci/test-runner.ts +129 -0
- package/src/config.ts +99 -0
- package/src/deploy/canary-deploy.ts +211 -0
- package/src/deploy/landing-queue.ts +222 -0
- package/src/headless/answer-injector.ts +99 -0
- package/src/headless/exit-codes.ts +32 -0
- package/src/headless/idle-detector.ts +76 -0
- package/src/headless/jsonl-stream.ts +90 -0
- package/src/headless/orchestrator.ts +207 -0
- package/{dist/index.js → src/index.ts} +30 -9
- package/src/tools/ci_status.ts +137 -0
- package/src/types.ts +149 -0
- package/src/workflow/deployment-workflow.ts +153 -0
- package/dist/ci/pipeline.d.ts +0 -43
- package/dist/ci/pipeline.d.ts.map +0 -1
- package/dist/ci/pipeline.js +0 -107
- package/dist/ci/pipeline.js.map +0 -1
- package/dist/ci/pr-creator.d.ts +0 -17
- package/dist/ci/pr-creator.d.ts.map +0 -1
- package/dist/ci/pr-creator.js +0 -67
- package/dist/ci/pr-creator.js.map +0 -1
- package/dist/ci/report.d.ts +0 -14
- package/dist/ci/report.d.ts.map +0 -1
- package/dist/ci/report.js +0 -51
- package/dist/ci/report.js.map +0 -1
- package/dist/ci/test-runner.d.ts +0 -10
- package/dist/ci/test-runner.d.ts.map +0 -1
- package/dist/ci/test-runner.js +0 -111
- package/dist/ci/test-runner.js.map +0 -1
- package/dist/config.d.ts +0 -33
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -67
- package/dist/config.js.map +0 -1
- package/dist/deploy/canary-deploy.d.ts +0 -80
- package/dist/deploy/canary-deploy.d.ts.map +0 -1
- package/dist/deploy/canary-deploy.js +0 -145
- package/dist/deploy/canary-deploy.js.map +0 -1
- package/dist/deploy/landing-queue.d.ts +0 -83
- package/dist/deploy/landing-queue.d.ts.map +0 -1
- package/dist/deploy/landing-queue.js +0 -172
- package/dist/deploy/landing-queue.js.map +0 -1
- package/dist/headless/answer-injector.d.ts +0 -27
- package/dist/headless/answer-injector.d.ts.map +0 -1
- package/dist/headless/answer-injector.js +0 -80
- package/dist/headless/answer-injector.js.map +0 -1
- package/dist/headless/exit-codes.d.ts +0 -13
- package/dist/headless/exit-codes.d.ts.map +0 -1
- package/dist/headless/exit-codes.js +0 -29
- package/dist/headless/exit-codes.js.map +0 -1
- package/dist/headless/idle-detector.d.ts +0 -32
- package/dist/headless/idle-detector.d.ts.map +0 -1
- package/dist/headless/idle-detector.js +0 -62
- package/dist/headless/idle-detector.js.map +0 -1
- package/dist/headless/jsonl-stream.d.ts +0 -28
- package/dist/headless/jsonl-stream.d.ts.map +0 -1
- package/dist/headless/jsonl-stream.js +0 -65
- package/dist/headless/jsonl-stream.js.map +0 -1
- package/dist/headless/orchestrator.d.ts +0 -63
- package/dist/headless/orchestrator.d.ts.map +0 -1
- package/dist/headless/orchestrator.js +0 -156
- package/dist/headless/orchestrator.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/tools/ci_status.d.ts +0 -40
- package/dist/tools/ci_status.d.ts.map +0 -1
- package/dist/tools/ci_status.js +0 -110
- package/dist/tools/ci_status.js.map +0 -1
- package/dist/types.d.ts +0 -93
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -17
- package/dist/types.js.map +0 -1
- package/dist/workflow/deployment-workflow.d.ts +0 -56
- package/dist/workflow/deployment-workflow.d.ts.map +0 -1
- package/dist/workflow/deployment-workflow.js +0 -95
- package/dist/workflow/deployment-workflow.js.map +0 -1
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Quick Start - pi-cicd
|
|
2
|
+
|
|
3
|
+
## Installation
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pi install npm:pi-cicd
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Basic Usage
|
|
10
|
+
|
|
11
|
+
### 1. Run Pipeline
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Run default pipeline
|
|
15
|
+
/ci run
|
|
16
|
+
|
|
17
|
+
# Run specific environment
|
|
18
|
+
/ci run production
|
|
19
|
+
|
|
20
|
+
# Run with variables
|
|
21
|
+
/ci run staging --var=VERSION=1.2.3
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### 2. Check Status
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Check current run status
|
|
28
|
+
/ci status
|
|
29
|
+
|
|
30
|
+
# Check specific run
|
|
31
|
+
/ci status --run-id=abc123
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 3. Deploy
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Deploy to environment
|
|
38
|
+
/ci deploy production
|
|
39
|
+
|
|
40
|
+
# Deploy with confirmation
|
|
41
|
+
/ci deploy staging --confirm
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 4. Canary Deployment
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Start canary (10% traffic)
|
|
48
|
+
/ci canary deploy --percentage 10
|
|
49
|
+
|
|
50
|
+
# Increase canary
|
|
51
|
+
/ci canary promote --percentage 25
|
|
52
|
+
|
|
53
|
+
# Rollback canary
|
|
54
|
+
/ci canary rollback
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Examples
|
|
58
|
+
|
|
59
|
+
### Example: Production Deploy
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
/ci run production
|
|
63
|
+
|
|
64
|
+
Output:
|
|
65
|
+
## CI/CD Pipeline: production
|
|
66
|
+
|
|
67
|
+
### Stage: build
|
|
68
|
+
✅ npm install (23s)
|
|
69
|
+
✅ npm run build (45s)
|
|
70
|
+
|
|
71
|
+
### Stage: test
|
|
72
|
+
✅ Unit tests (89 tests, 12s)
|
|
73
|
+
✅ Integration tests (34 tests, 28s)
|
|
74
|
+
|
|
75
|
+
### Stage: deploy
|
|
76
|
+
🚀 Deploying to production...
|
|
77
|
+
|
|
78
|
+
✅ Pipeline complete (2m 34s)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Example: Headless CI
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# In GitHub Actions
|
|
85
|
+
- name: Run Pi CI
|
|
86
|
+
run: |
|
|
87
|
+
pi ci run --headless --exit-code
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Exit Codes
|
|
91
|
+
|
|
92
|
+
| Code | Meaning |
|
|
93
|
+
|------|---------|
|
|
94
|
+
| 0 | Success |
|
|
95
|
+
| 1 | General error |
|
|
96
|
+
| 2 | Timeout |
|
|
97
|
+
| 10 | Blocked (verification failed) |
|
|
98
|
+
| 11 | Cancelled |
|
|
99
|
+
| 12 | Deployment failed |
|
|
@@ -3,6 +3,20 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Registers the /ci status command and CI lifecycle hooks.
|
|
5
5
|
*/
|
|
6
|
+
|
|
7
|
+
import type { CIEvent, CIOptions, ExitCode } from "./src/types.ts";
|
|
8
|
+
import { EXIT_CODES } from "./src/types.ts";
|
|
9
|
+
import {
|
|
10
|
+
ciStatusHandler,
|
|
11
|
+
createRunTracker,
|
|
12
|
+
clearRuns,
|
|
13
|
+
registerRun,
|
|
14
|
+
type CIRunRecord,
|
|
15
|
+
} from "./src/tools/ci_status.ts";
|
|
16
|
+
import { CIPipeline, type PipelineResult } from "./src/ci/pipeline.ts";
|
|
17
|
+
import { generateReport } from "./src/ci/report.ts";
|
|
18
|
+
|
|
19
|
+
// Re-export for consumers
|
|
6
20
|
export { EXIT_CODES } from "./src/types.ts";
|
|
7
21
|
export { resolveExitCode } from "./src/headless/exit-codes.ts";
|
|
8
22
|
export { loadAnswers, matchAnswer, parseAnswers } from "./src/headless/answer-injector.ts";
|
|
@@ -16,15 +30,23 @@ export { generateReport } from "./src/ci/report.ts";
|
|
|
16
30
|
export { ciStatusHandler, registerRun, clearRuns, createRunTracker } from "./src/tools/ci_status.ts";
|
|
17
31
|
export { loadCiConfig, DEFAULT_CONFIG } from "./src/config.ts";
|
|
18
32
|
export type { PiCiConfig } from "./src/config.ts";
|
|
33
|
+
|
|
19
34
|
/**
|
|
20
35
|
* Extension API type (minimal — avoids hard dep on pi-coding-agent types).
|
|
21
36
|
*/
|
|
22
37
|
interface ExtensionAPI {
|
|
23
|
-
|
|
24
|
-
|
|
38
|
+
registerCommand?: (name: string, handler: (args: unknown) => string | Promise<string>) => void;
|
|
39
|
+
on?: (event: string, handler: (...args: unknown[]) => void) => void;
|
|
25
40
|
}
|
|
41
|
+
|
|
26
42
|
/**
|
|
27
43
|
* Default export — Pi extension registration.
|
|
28
44
|
*/
|
|
29
|
-
export default function piCiExtension(pi: ExtensionAPI): void
|
|
30
|
-
|
|
45
|
+
export default function piCiExtension(pi: ExtensionAPI): void {
|
|
46
|
+
// Register /ci status command
|
|
47
|
+
if (pi.registerCommand) {
|
|
48
|
+
pi.registerCommand("ci", (args: unknown) => {
|
|
49
|
+
return ciStatusHandler(args);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
package/install.mjs
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Install script for <PKG-NAME>
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as fs from "node:fs";
|
|
7
|
+
import * as os from "node:os";
|
|
8
|
+
import * as path from "node:path";
|
|
9
|
+
|
|
10
|
+
const home = os.homedir();
|
|
11
|
+
const agentDir = path.join(home, ".pi", "agent");
|
|
12
|
+
const pkgName = path.basename(process.cwd());
|
|
13
|
+
const configPath = path.join(agentDir, `${pkgName}.json`);
|
|
14
|
+
|
|
15
|
+
// Create config directory
|
|
16
|
+
fs.mkdirSync(agentDir, { recursive: true });
|
|
17
|
+
|
|
18
|
+
// Check if config already exists
|
|
19
|
+
if (!fs.existsSync(configPath)) {
|
|
20
|
+
const defaultConfig = {
|
|
21
|
+
enabled: true
|
|
22
|
+
};
|
|
23
|
+
fs.writeFileSync(configPath, `${JSON.stringify(defaultConfig, null, 2)}\n`, "utf-8");
|
|
24
|
+
console.log(`Created default ${pkgName} config: ${configPath}`);
|
|
25
|
+
} else {
|
|
26
|
+
console.log(`${pkgName} config already exists: ${configPath}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.log(`\nInstall the published package in Pi with:`);
|
|
30
|
+
console.log(` pi install npm:@baphuongna/${pkgName}`);
|
|
31
|
+
console.log(`\nFor local development from a cloned repo:`);
|
|
32
|
+
console.log(` pi install .`);
|
|
33
|
+
console.log(`\nVerify installation:`);
|
|
34
|
+
console.log(` pi list\n`);
|
package/package.json
CHANGED
|
@@ -1,25 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-cicd",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Extension for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./
|
|
7
|
-
"module": "./
|
|
8
|
-
"types": "./
|
|
6
|
+
"main": "./index.ts",
|
|
7
|
+
"module": "./index.ts",
|
|
8
|
+
"types": "./index.ts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
-
"import": "./
|
|
12
|
-
"types": "./
|
|
11
|
+
"import": "./index.ts",
|
|
12
|
+
"types": "./index.ts"
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
|
-
"scripts": {
|
|
16
|
-
"build": "tsc --noEmitOnError false",
|
|
17
|
-
"typecheck": "tsc --noEmit",
|
|
18
|
-
"test": "npx tsx --test --test-concurrency=1 --test-timeout=30000 test/unit/*.test.ts"
|
|
19
|
-
},
|
|
20
15
|
"files": [
|
|
21
|
-
"
|
|
16
|
+
"*.ts",
|
|
17
|
+
"*.mjs",
|
|
18
|
+
"src/**/*.ts",
|
|
19
|
+
"skills/**/*",
|
|
20
|
+
"README.md",
|
|
21
|
+
"docs/",
|
|
22
|
+
"CHANGELOG.md",
|
|
23
|
+
"LICENSE"
|
|
22
24
|
],
|
|
25
|
+
"pi": {
|
|
26
|
+
"extensions": [
|
|
27
|
+
"./index.ts"
|
|
28
|
+
]
|
|
29
|
+
},
|
|
23
30
|
"keywords": [
|
|
24
31
|
"pi",
|
|
25
32
|
"coding-agent",
|
|
@@ -27,15 +34,8 @@
|
|
|
27
34
|
],
|
|
28
35
|
"author": "BaphuongNA",
|
|
29
36
|
"license": "MIT",
|
|
30
|
-
"
|
|
31
|
-
"extensions": [
|
|
32
|
-
"./index.ts"
|
|
33
|
-
]
|
|
34
|
-
},
|
|
37
|
+
"repository": "https://github.com/baphuongna/pi-cicd",
|
|
35
38
|
"peerDependencies": {
|
|
36
39
|
"typescript": "^5.0.0"
|
|
37
|
-
},
|
|
38
|
-
"devDependencies": {
|
|
39
|
-
"tsx": "^4.19.0"
|
|
40
40
|
}
|
|
41
41
|
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: intelligent-deploy
|
|
3
|
+
description: Canary deployment with monitoring and landing queue management
|
|
4
|
+
triggers:
|
|
5
|
+
- deploy
|
|
6
|
+
- canary
|
|
7
|
+
- rollout
|
|
8
|
+
- landing queue
|
|
9
|
+
- production
|
|
10
|
+
requirements:
|
|
11
|
+
tools: [bash]
|
|
12
|
+
context: [deployment target]
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Intelligent Deploy Skill
|
|
16
|
+
|
|
17
|
+
## Objective
|
|
18
|
+
Execute safe deployments with canary monitoring, automatic rollback, and landing queue management.
|
|
19
|
+
|
|
20
|
+
## When to Use
|
|
21
|
+
- When user asks to "deploy" or "ship to production"
|
|
22
|
+
- When running CI/CD pipelines
|
|
23
|
+
- When managing multiple deployments
|
|
24
|
+
- When requiring gradual rollouts with monitoring
|
|
25
|
+
|
|
26
|
+
## Workflow
|
|
27
|
+
|
|
28
|
+
### Step 1: Canary Deployment
|
|
29
|
+
```typescript
|
|
30
|
+
import { CanaryDeploy } from '../../src/deploy/canary-deploy';
|
|
31
|
+
|
|
32
|
+
const canary = new CanaryDeploy({
|
|
33
|
+
initialPercentage: 10,
|
|
34
|
+
incrementPercentage: 10,
|
|
35
|
+
stepInterval: 60000, // 1 minute
|
|
36
|
+
totalDuration: 300000, // 5 minutes
|
|
37
|
+
metrics: {
|
|
38
|
+
successRate: { min: 95 },
|
|
39
|
+
latency: { max: 500 },
|
|
40
|
+
errorRate: { max: 5 },
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const result = await canary.deploy({
|
|
45
|
+
name: 'production',
|
|
46
|
+
url: 'https://api.example.com',
|
|
47
|
+
healthy: true,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
console.log(canary.formatReport(result));
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Step 2: Landing Queue
|
|
54
|
+
```typescript
|
|
55
|
+
import { LandingQueue } from '../../src/deploy/landing-queue';
|
|
56
|
+
|
|
57
|
+
const queue = new LandingQueue();
|
|
58
|
+
|
|
59
|
+
// Add to queue
|
|
60
|
+
queue.enqueue('v1.2.0', 'production', 'New feature release');
|
|
61
|
+
queue.enqueue('v1.2.1', 'production', 'Bug fix');
|
|
62
|
+
|
|
63
|
+
// Process queue
|
|
64
|
+
while (true) {
|
|
65
|
+
const next = await queue.startNext();
|
|
66
|
+
if (!next) break;
|
|
67
|
+
|
|
68
|
+
// Deploy
|
|
69
|
+
const success = await deploy(next);
|
|
70
|
+
|
|
71
|
+
// Mark complete
|
|
72
|
+
queue.complete(next.id, success);
|
|
73
|
+
|
|
74
|
+
if (!success) break; // Stop on failure
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Step 3: Monitor and Rollback
|
|
79
|
+
```typescript
|
|
80
|
+
// Automatic rollback on issues
|
|
81
|
+
if (result.rolledBack) {
|
|
82
|
+
console.log('Deployment rolled back due to issues');
|
|
83
|
+
await canary.rollback(target);
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Canary Configuration
|
|
88
|
+
|
|
89
|
+
| Parameter | Default | Description |
|
|
90
|
+
|-----------|---------|-------------|
|
|
91
|
+
| initialPercentage | 10% | Starting traffic |
|
|
92
|
+
| incrementPercentage | 10% | Traffic increase per step |
|
|
93
|
+
| stepInterval | 1 min | Time between increments |
|
|
94
|
+
| totalDuration | 5 min | Total deployment time |
|
|
95
|
+
|
|
96
|
+
### Metrics Thresholds
|
|
97
|
+
|
|
98
|
+
| Metric | Threshold | Action |
|
|
99
|
+
|--------|-----------|--------|
|
|
100
|
+
| Success Rate | > 95% | Continue |
|
|
101
|
+
| Latency | < 500ms | Continue |
|
|
102
|
+
| Error Rate | < 5% | Continue |
|
|
103
|
+
|
|
104
|
+
If metrics drop below thresholds:
|
|
105
|
+
- Warning at threshold
|
|
106
|
+
- Auto-rollback at critical level (90% success, 10% errors)
|
|
107
|
+
|
|
108
|
+
## Landing Queue Commands
|
|
109
|
+
|
|
110
|
+
### Enqueue Deployment
|
|
111
|
+
```typescript
|
|
112
|
+
queue.enqueue('v1.2.0', 'production', 'Feature release');
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Process Queue
|
|
116
|
+
```typescript
|
|
117
|
+
while (const next = queue.startNext()) {
|
|
118
|
+
await deploy(next);
|
|
119
|
+
queue.complete(next.id, success);
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### View Status
|
|
124
|
+
```typescript
|
|
125
|
+
console.log(queue.formatReport());
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Examples
|
|
129
|
+
|
|
130
|
+
### Deploy with Canary
|
|
131
|
+
```
|
|
132
|
+
User: Deploy v1.2.0 to production with canary
|
|
133
|
+
Agent:
|
|
134
|
+
const result = await canary.deploy({ name: 'production', url: '...', healthy: true });
|
|
135
|
+
if (result.success) {
|
|
136
|
+
console.log('Deployment successful!');
|
|
137
|
+
} else {
|
|
138
|
+
console.log('Rolled back - check issues');
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Process Landing Queue
|
|
143
|
+
```
|
|
144
|
+
User: Queue these deployments: v1.2.0, v1.2.1, v1.2.2
|
|
145
|
+
Agent:
|
|
146
|
+
queue.enqueue('v1.2.0', 'production');
|
|
147
|
+
queue.enqueue('v1.2.1', 'production');
|
|
148
|
+
queue.enqueue('v1.2.2', 'staging');
|
|
149
|
+
|
|
150
|
+
const result = await queue.processAll();
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### View Queue Status
|
|
154
|
+
```
|
|
155
|
+
User: Show me the current deployment queue
|
|
156
|
+
Agent:
|
|
157
|
+
console.log(queue.formatReport());
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Output Format
|
|
161
|
+
|
|
162
|
+
### Canary Report
|
|
163
|
+
```markdown
|
|
164
|
+
## Canary Deployment Report
|
|
165
|
+
**Status:** ✅ SUCCESS
|
|
166
|
+
**Final Traffic:** 100%
|
|
167
|
+
**Rolled Back:** No
|
|
168
|
+
|
|
169
|
+
### Metrics History
|
|
170
|
+
| Time | Success | Latency | Error Rate |
|
|
171
|
+
|------|---------|---------|------------|
|
|
172
|
+
| 0min | 98.5% | 120ms | 1.2% |
|
|
173
|
+
| 1min | 99.1% | 115ms | 0.8% |
|
|
174
|
+
| 2min | 99.3% | 110ms | 0.5% |
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Landing Queue Report
|
|
178
|
+
```markdown
|
|
179
|
+
## Landing Queue Report
|
|
180
|
+
**Total:** 5 | **Pending:** 2 | **Deploying:** 1 | **Deployed:** 2 | **Failed:** 0
|
|
181
|
+
|
|
182
|
+
### Currently Deploying
|
|
183
|
+
**v1.2.1** -> production
|
|
184
|
+
Status: deploying
|
|
185
|
+
|
|
186
|
+
### Queue
|
|
187
|
+
| # | Version | Environment | Message |
|
|
188
|
+
|---|--------|------------|---------|
|
|
189
|
+
| 1 | v1.2.2 | production | Hotfix |
|
|
190
|
+
| 2 | v1.2.3 | staging | New feature |
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Integration
|
|
194
|
+
|
|
195
|
+
### With pi-pipeline
|
|
196
|
+
```typescript
|
|
197
|
+
// Run as part of ship workflow
|
|
198
|
+
const gates = new QualityGates();
|
|
199
|
+
const gatesResult = await gates.run('pre-push');
|
|
200
|
+
|
|
201
|
+
if (gatesResult.passed) {
|
|
202
|
+
const result = await canary.deploy(target);
|
|
203
|
+
if (!result.success) {
|
|
204
|
+
throw new Error('Deployment failed');
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### With pi-audit
|
|
210
|
+
```typescript
|
|
211
|
+
// Security scan before deploy
|
|
212
|
+
const shield = new AgentShield();
|
|
213
|
+
const scan = shield.scan(deploymentCode);
|
|
214
|
+
|
|
215
|
+
if (!scan.passed) {
|
|
216
|
+
console.log('Security issues must be fixed');
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### With pi-recollect
|
|
222
|
+
```typescript
|
|
223
|
+
// Remember deployment
|
|
224
|
+
await memory.remember(
|
|
225
|
+
'deployment',
|
|
226
|
+
`Deployed ${version} to ${environment}`,
|
|
227
|
+
'observation'
|
|
228
|
+
);
|
|
229
|
+
```
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi-ci — CI pipeline wrapper.
|
|
3
|
+
*
|
|
4
|
+
* Provides single, plan, review, and supervised execution modes.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { CIEvent, CIOptions, CIPipelineMode, ExitCode, TestSummary } from "../types.ts";
|
|
8
|
+
import { EXIT_CODES } from "../types.ts";
|
|
9
|
+
import { HeadlessOrchestrator, type OrchestratorHooks } from "../headless/orchestrator.ts";
|
|
10
|
+
import { loadAnswers } from "../headless/answer-injector.ts";
|
|
11
|
+
import { parseTestResults } from "./test-runner.ts";
|
|
12
|
+
import { generateReport } from "./report.ts";
|
|
13
|
+
|
|
14
|
+
export interface PipelineResult {
|
|
15
|
+
exitCode: ExitCode;
|
|
16
|
+
events: CIEvent[];
|
|
17
|
+
report: string;
|
|
18
|
+
testSummary?: TestSummary;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class CIPipeline {
|
|
22
|
+
private readonly options: CIOptions;
|
|
23
|
+
private readonly hooks: OrchestratorHooks;
|
|
24
|
+
|
|
25
|
+
constructor(options: CIOptions, hooks: OrchestratorHooks) {
|
|
26
|
+
this.options = options;
|
|
27
|
+
this.hooks = hooks;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Execute the pipeline in the configured mode.
|
|
32
|
+
*/
|
|
33
|
+
async execute(): Promise<PipelineResult> {
|
|
34
|
+
switch (this.options.mode) {
|
|
35
|
+
case "single":
|
|
36
|
+
return this.executeSingle();
|
|
37
|
+
case "plan":
|
|
38
|
+
return this.executePlan();
|
|
39
|
+
case "review":
|
|
40
|
+
return this.executeReview();
|
|
41
|
+
case "supervised":
|
|
42
|
+
return this.executeSupervised();
|
|
43
|
+
default:
|
|
44
|
+
throw new Error(`Unknown CI pipeline mode: ${this.options.mode as string}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Single task mode — run one prompt to completion.
|
|
50
|
+
*/
|
|
51
|
+
private async executeSingle(): Promise<PipelineResult> {
|
|
52
|
+
const answers = await this.loadAnswers();
|
|
53
|
+
const orchestrator = new HeadlessOrchestrator(answers, this.options, this.hooks);
|
|
54
|
+
const result = await orchestrator.run(this.options.prompt, "single");
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
exitCode: result.exitCode,
|
|
58
|
+
events: result.events,
|
|
59
|
+
report: generateReport(result.events),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Plan mode — execute steps from a plan file.
|
|
65
|
+
*
|
|
66
|
+
* Each step becomes a sequential orchestrator invocation. If any step fails,
|
|
67
|
+
* the pipeline stops.
|
|
68
|
+
*/
|
|
69
|
+
private async executePlan(): Promise<PipelineResult> {
|
|
70
|
+
const allEvents: CIEvent[] = [];
|
|
71
|
+
let finalExitCode: ExitCode = EXIT_CODES.SUCCESS;
|
|
72
|
+
let totalDuration = 0;
|
|
73
|
+
|
|
74
|
+
// Plan steps are provided via the executeStep hook — the orchestrator
|
|
75
|
+
// iterates over steps internally.
|
|
76
|
+
const answers = await this.loadAnswers();
|
|
77
|
+
const orchestrator = new HeadlessOrchestrator(answers, this.options, this.hooks);
|
|
78
|
+
const result = await orchestrator.run(this.options.prompt, "plan");
|
|
79
|
+
|
|
80
|
+
allEvents.push(...result.events);
|
|
81
|
+
finalExitCode = result.exitCode;
|
|
82
|
+
totalDuration = result.durationMs;
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
exitCode: finalExitCode,
|
|
86
|
+
events: allEvents,
|
|
87
|
+
report: generateReport(allEvents),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* PR review mode — review a PR by number.
|
|
93
|
+
*/
|
|
94
|
+
private async executeReview(): Promise<PipelineResult> {
|
|
95
|
+
const answers = await this.loadAnswers();
|
|
96
|
+
const orchestrator = new HeadlessOrchestrator(answers, this.options, this.hooks);
|
|
97
|
+
const prompt = this.options.prNumber
|
|
98
|
+
? `Review PR #${this.options.prNumber}`
|
|
99
|
+
: this.options.prompt;
|
|
100
|
+
const result = await orchestrator.run(prompt, "single");
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
exitCode: result.exitCode,
|
|
104
|
+
events: result.events,
|
|
105
|
+
report: generateReport(result.events),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Supervised mode — stdin/stdout forwarding for an external orchestrator.
|
|
111
|
+
*/
|
|
112
|
+
private async executeSupervised(): Promise<PipelineResult> {
|
|
113
|
+
const answers = await this.loadAnswers();
|
|
114
|
+
const orchestrator = new HeadlessOrchestrator(answers, this.options, this.hooks);
|
|
115
|
+
const result = await orchestrator.run(this.options.prompt, "single");
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
exitCode: result.exitCode,
|
|
119
|
+
events: result.events,
|
|
120
|
+
report: generateReport(result.events),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private async loadAnswers() {
|
|
125
|
+
if (this.options.answersFile) {
|
|
126
|
+
return loadAnswers(this.options.answersFile);
|
|
127
|
+
}
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi-ci — PR creation via the GitHub CLI (`gh`).
|
|
3
|
+
*
|
|
4
|
+
* Wraps `gh pr create` with structured error handling.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { execFile } from "node:child_process";
|
|
8
|
+
import type { PROptions, PRResult } from "../types.ts";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create a pull request using the `gh` CLI.
|
|
12
|
+
*
|
|
13
|
+
* Throws if `gh` is not installed or the command fails.
|
|
14
|
+
*/
|
|
15
|
+
export async function createPR(options: PROptions): Promise<PRResult> {
|
|
16
|
+
const args = ["pr", "create", "--title", options.title];
|
|
17
|
+
|
|
18
|
+
if (options.body) {
|
|
19
|
+
args.push("--body", options.body);
|
|
20
|
+
}
|
|
21
|
+
if (options.base) {
|
|
22
|
+
args.push("--base", options.base);
|
|
23
|
+
}
|
|
24
|
+
if (options.head) {
|
|
25
|
+
args.push("--head", options.head);
|
|
26
|
+
}
|
|
27
|
+
if (options.draft) {
|
|
28
|
+
args.push("--draft");
|
|
29
|
+
}
|
|
30
|
+
for (const label of options.labels ?? []) {
|
|
31
|
+
args.push("--label", label);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const output = await runGh(args);
|
|
35
|
+
|
|
36
|
+
// Parse the PR URL from the output
|
|
37
|
+
const urlMatch = output.match(/https:\/\/github\.com\/[^\s]+\/pull\/(\d+)/);
|
|
38
|
+
if (!urlMatch) {
|
|
39
|
+
throw new Error(`Failed to parse PR URL from gh output: ${output}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
url: urlMatch[0],
|
|
44
|
+
number: parseInt(urlMatch[1], 10),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Detect the default (base) branch for the current repository.
|
|
50
|
+
*/
|
|
51
|
+
export async function detectBaseBranch(): Promise<string> {
|
|
52
|
+
const output = await runGh(["repo", "view", "--json", "defaultBranchRef", "--jq", ".defaultBranchRef.name"]);
|
|
53
|
+
return output.trim() || "main";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Execute a `gh` command and return stdout.
|
|
58
|
+
*/
|
|
59
|
+
function runGh(args: string[]): Promise<string> {
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
execFile("gh", args, { timeout: 60_000 }, (err, stdout, stderr) => {
|
|
62
|
+
if (err) {
|
|
63
|
+
const message = stderr?.trim() || err.message;
|
|
64
|
+
if (err.code === "ENOENT") {
|
|
65
|
+
reject(new Error("gh CLI is not installed. Install it from https://cli.github.com"));
|
|
66
|
+
} else {
|
|
67
|
+
reject(new Error(`gh ${args.join(" ")} failed: ${message}`));
|
|
68
|
+
}
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
resolve(stdout);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|