pi-cicd 0.1.1 → 0.3.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 (80) hide show
  1. package/README.md +62 -0
  2. package/dist/ci/pipeline.d.ts +43 -0
  3. package/dist/ci/pipeline.d.ts.map +1 -0
  4. package/dist/ci/pipeline.js +107 -0
  5. package/dist/ci/pipeline.js.map +1 -0
  6. package/dist/ci/pr-creator.d.ts +17 -0
  7. package/dist/ci/pr-creator.d.ts.map +1 -0
  8. package/dist/ci/pr-creator.js +67 -0
  9. package/dist/ci/pr-creator.js.map +1 -0
  10. package/dist/ci/report.d.ts +14 -0
  11. package/dist/ci/report.d.ts.map +1 -0
  12. package/dist/ci/report.js +51 -0
  13. package/dist/ci/report.js.map +1 -0
  14. package/dist/ci/test-runner.d.ts +10 -0
  15. package/dist/ci/test-runner.d.ts.map +1 -0
  16. package/dist/ci/test-runner.js +111 -0
  17. package/dist/ci/test-runner.js.map +1 -0
  18. package/dist/config.d.ts +33 -0
  19. package/dist/config.d.ts.map +1 -0
  20. package/dist/config.js +67 -0
  21. package/dist/config.js.map +1 -0
  22. package/dist/deploy/canary-deploy.d.ts +80 -0
  23. package/dist/deploy/canary-deploy.d.ts.map +1 -0
  24. package/dist/deploy/canary-deploy.js +145 -0
  25. package/dist/deploy/canary-deploy.js.map +1 -0
  26. package/dist/deploy/landing-queue.d.ts +83 -0
  27. package/dist/deploy/landing-queue.d.ts.map +1 -0
  28. package/dist/deploy/landing-queue.js +172 -0
  29. package/dist/deploy/landing-queue.js.map +1 -0
  30. package/dist/headless/answer-injector.d.ts +27 -0
  31. package/dist/headless/answer-injector.d.ts.map +1 -0
  32. package/dist/headless/answer-injector.js +80 -0
  33. package/dist/headless/answer-injector.js.map +1 -0
  34. package/dist/headless/exit-codes.d.ts +13 -0
  35. package/dist/headless/exit-codes.d.ts.map +1 -0
  36. package/dist/headless/exit-codes.js +29 -0
  37. package/dist/headless/exit-codes.js.map +1 -0
  38. package/dist/headless/idle-detector.d.ts +32 -0
  39. package/dist/headless/idle-detector.d.ts.map +1 -0
  40. package/dist/headless/idle-detector.js +62 -0
  41. package/dist/headless/idle-detector.js.map +1 -0
  42. package/dist/headless/jsonl-stream.d.ts +28 -0
  43. package/dist/headless/jsonl-stream.d.ts.map +1 -0
  44. package/dist/headless/jsonl-stream.js +65 -0
  45. package/dist/headless/jsonl-stream.js.map +1 -0
  46. package/dist/headless/orchestrator.d.ts +63 -0
  47. package/dist/headless/orchestrator.d.ts.map +1 -0
  48. package/dist/headless/orchestrator.js +156 -0
  49. package/dist/headless/orchestrator.js.map +1 -0
  50. package/{index.ts → dist/index.d.ts} +4 -26
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +31 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/tools/ci_status.d.ts +40 -0
  55. package/dist/tools/ci_status.d.ts.map +1 -0
  56. package/dist/tools/ci_status.js +110 -0
  57. package/dist/tools/ci_status.js.map +1 -0
  58. package/dist/types.d.ts +93 -0
  59. package/dist/types.d.ts.map +1 -0
  60. package/dist/types.js +17 -0
  61. package/dist/types.js.map +1 -0
  62. package/dist/workflow/deployment-workflow.d.ts +56 -0
  63. package/dist/workflow/deployment-workflow.d.ts.map +1 -0
  64. package/dist/workflow/deployment-workflow.js +95 -0
  65. package/dist/workflow/deployment-workflow.js.map +1 -0
  66. package/package.json +26 -26
  67. package/AGENTS.md +0 -25
  68. package/src/ci/pipeline.ts +0 -130
  69. package/src/ci/pr-creator.ts +0 -74
  70. package/src/ci/report.ts +0 -65
  71. package/src/ci/test-runner.ts +0 -129
  72. package/src/config.ts +0 -99
  73. package/src/headless/answer-injector.ts +0 -98
  74. package/src/headless/exit-codes.ts +0 -32
  75. package/src/headless/idle-detector.ts +0 -76
  76. package/src/headless/jsonl-stream.ts +0 -90
  77. package/src/headless/orchestrator.ts +0 -206
  78. package/src/tools/ci_status.ts +0 -137
  79. package/src/types.ts +0 -149
  80. package/tsconfig.json +0 -19
@@ -1,206 +0,0 @@
1
- /**
2
- * pi-ci — Headless orchestrator.
3
- *
4
- * Ties together exit codes, answer injection, idle detection, and JSONL
5
- * streaming into a single execution loop.
6
- */
7
-
8
- import type { ExitCode, CIEvent, CIOptions } from "../types.ts";
9
- import { EXIT_CODES } from "../types.ts";
10
- import { resolveExitCode } from "./exit-codes.ts";
11
- import { matchAnswer, type AnswerEntry } from "./answer-injector.ts";
12
- import { IdleDetector } from "./idle-detector.ts";
13
- import { CIEventCollector, writeCIEvent } from "./jsonl-stream.ts";
14
- import type { Writable } from "node:stream";
15
-
16
- export interface OrchestratorResult {
17
- exitCode: ExitCode;
18
- events: CIEvent[];
19
- durationMs: number;
20
- }
21
-
22
- export interface OrchestratorHooks {
23
- /** Called for each step of execution. Return a status string to signal completion. */
24
- executeStep: (
25
- prompt: string,
26
- injectAnswer: (question: string) => string | undefined,
27
- ) => Promise<StepResult>;
28
- /** Optional writable for JSONL streaming. */
29
- outputStream?: Writable;
30
- }
31
-
32
- export interface StepResult {
33
- status: string;
34
- edits?: { file: string; lines_added: number; lines_removed: number }[];
35
- tests?: { command: string; passed: number; failed: number }[];
36
- cost?: { tokens: { input: number; output: number }; cost_usd: number };
37
- }
38
-
39
- const RESTART_CONFIG = {
40
- baseDelayMs: 5_000,
41
- maxDelayMs: 30_000,
42
- backoffMultiplier: 2,
43
- };
44
-
45
- export class HeadlessOrchestrator {
46
- private readonly collector = new CIEventCollector();
47
- private readonly answers: AnswerEntry[];
48
- private readonly idleTimeoutMs: number;
49
- private readonly maxRetries: number;
50
- private readonly hooks: OrchestratorHooks;
51
-
52
- constructor(
53
- answers: AnswerEntry[],
54
- options: CIOptions,
55
- hooks: OrchestratorHooks,
56
- ) {
57
- this.answers = answers;
58
- this.idleTimeoutMs = options.idleTimeoutMs ?? 15_000;
59
- this.maxRetries = options.maxRetries ?? 3;
60
- this.hooks = hooks;
61
- }
62
-
63
- /**
64
- * Run the orchestrator loop.
65
- *
66
- * 1. Emit ci_start
67
- * 2. Execute steps via the hook, checking for answer injection
68
- * 3. On idle timeout → retry with exponential backoff
69
- * 4. Emit ci_end with the resolved exit code
70
- */
71
- async run(prompt: string, mode: "single" | "plan"): Promise<OrchestratorResult> {
72
- const startTime = Date.now();
73
-
74
- const startEvent: CIEvent = {
75
- type: "ci_start",
76
- timestamp: new Date().toISOString(),
77
- task: prompt,
78
- mode,
79
- };
80
- this.emit(startEvent);
81
-
82
- let lastExitCode: ExitCode = EXIT_CODES.ERROR;
83
- let retries = 0;
84
-
85
- while (retries <= this.maxRetries) {
86
- const result = await this.runAttempt(prompt);
87
- lastExitCode = result;
88
-
89
- if (lastExitCode === EXIT_CODES.SUCCESS) break;
90
- if (lastExitCode === EXIT_CODES.BLOCKED || lastExitCode === EXIT_CODES.CANCELLED) break;
91
-
92
- // Retry on error/timeout
93
- retries++;
94
- if (retries <= this.maxRetries) {
95
- const delay = Math.min(
96
- RESTART_CONFIG.baseDelayMs *
97
- Math.pow(RESTART_CONFIG.backoffMultiplier, retries - 1),
98
- RESTART_CONFIG.maxDelayMs,
99
- );
100
- await sleep(delay);
101
- }
102
- }
103
-
104
- const durationMs = Date.now() - startTime;
105
- const endEvent: CIEvent = {
106
- type: "ci_end",
107
- timestamp: new Date().toISOString(),
108
- exit_code: lastExitCode,
109
- duration_ms: durationMs,
110
- };
111
- this.emit(endEvent);
112
-
113
- return {
114
- exitCode: lastExitCode,
115
- events: this.collector.all(),
116
- durationMs,
117
- };
118
- }
119
-
120
- /**
121
- * Single attempt: run with idle detection.
122
- */
123
- private async runAttempt(prompt: string): Promise<ExitCode> {
124
- return new Promise<ExitCode>((resolve) => {
125
- let settled = false;
126
-
127
- const idle = new IdleDetector({
128
- idleTimeoutMs: this.idleTimeoutMs,
129
- onTimeout: () => {
130
- if (!settled) {
131
- settled = true;
132
- resolve(EXIT_CODES.TIMEOUT);
133
- }
134
- },
135
- });
136
-
137
- const injectAnswer = (question: string): string | undefined => {
138
- idle.reset();
139
- return matchAnswer(this.answers, question);
140
- };
141
-
142
- idle.start();
143
-
144
- this.hooks
145
- .executeStep(prompt, injectAnswer)
146
- .then((stepResult) => {
147
- if (!settled) {
148
- settled = true;
149
- idle.stop();
150
-
151
- // Emit detail events
152
- if (stepResult.edits) {
153
- for (const edit of stepResult.edits) {
154
- this.emit({
155
- type: "ci_edit",
156
- timestamp: new Date().toISOString(),
157
- file: edit.file,
158
- lines_added: edit.lines_added,
159
- lines_removed: edit.lines_removed,
160
- });
161
- }
162
- }
163
- if (stepResult.tests) {
164
- for (const t of stepResult.tests) {
165
- this.emit({
166
- type: "ci_test",
167
- timestamp: new Date().toISOString(),
168
- command: t.command,
169
- passed: t.passed,
170
- failed: t.failed,
171
- });
172
- }
173
- }
174
- if (stepResult.cost) {
175
- this.emit({
176
- type: "ci_cost",
177
- timestamp: new Date().toISOString(),
178
- tokens: stepResult.cost.tokens,
179
- cost_usd: stepResult.cost.cost_usd,
180
- });
181
- }
182
-
183
- resolve(resolveExitCode(stepResult.status));
184
- }
185
- })
186
- .catch(() => {
187
- if (!settled) {
188
- settled = true;
189
- idle.stop();
190
- resolve(EXIT_CODES.ERROR);
191
- }
192
- });
193
- });
194
- }
195
-
196
- private emit(event: CIEvent): void {
197
- this.collector.emit(event);
198
- if (this.hooks.outputStream) {
199
- writeCIEvent(this.hooks.outputStream, event);
200
- }
201
- }
202
- }
203
-
204
- function sleep(ms: number): Promise<void> {
205
- return new Promise((r) => setTimeout(r, ms));
206
- }
@@ -1,137 +0,0 @@
1
- /**
2
- * pi-ci — /ci status command handler.
3
- *
4
- * Shows the status of the current or last CI run.
5
- */
6
-
7
- import { CIEventCollector } from "../headless/jsonl-stream.ts";
8
- import { generateReport } from "../ci/report.ts";
9
- import type { CIEvent, CIEndEvent, ExitCode } from "../types.ts";
10
- import { isCIEndEvent } from "../headless/jsonl-stream.ts";
11
-
12
- export interface CIRunRecord {
13
- id: string;
14
- startTime: string;
15
- events: CIEvent[];
16
- exitCode?: ExitCode;
17
- durationMs?: number;
18
- }
19
-
20
- /**
21
- * Simple in-memory registry of CI runs (for the status command).
22
- */
23
- const runRegistry = new Map<string, CIRunRecord>();
24
-
25
- /**
26
- * Register a CI run for status lookups.
27
- */
28
- export function registerRun(record: CIRunRecord): void {
29
- runRegistry.set(record.id, record);
30
- }
31
-
32
- /**
33
- * Clear all registered runs (useful for testing).
34
- */
35
- export function clearRuns(): void {
36
- runRegistry.clear();
37
- }
38
-
39
- /**
40
- * Get a specific run by ID (prefix match supported).
41
- */
42
- export function getRun(id: string): CIRunRecord | undefined {
43
- // Exact match first
44
- if (runRegistry.has(id)) {
45
- return runRegistry.get(id);
46
- }
47
- // Prefix match
48
- for (const [key, value] of runRegistry) {
49
- if (key.startsWith(id)) {
50
- return value;
51
- }
52
- }
53
- return undefined;
54
- }
55
-
56
- /**
57
- * Handle the /ci status command.
58
- *
59
- * Returns a human-readable status string.
60
- */
61
- export function ciStatusHandler(args: unknown): string {
62
- const runId = typeof args === "string" ? args : undefined;
63
-
64
- if (runId) {
65
- const run = getRun(runId);
66
- if (!run) {
67
- return `No CI run found matching: ${runId}`;
68
- }
69
- return formatRunStatus(run);
70
- }
71
-
72
- // Show all runs
73
- if (runRegistry.size === 0) {
74
- return "No CI runs found.";
75
- }
76
-
77
- const lines: string[] = [];
78
- for (const run of runRegistry.values()) {
79
- lines.push(formatRunStatus(run));
80
- lines.push("");
81
- }
82
- return lines.join("\n").trimEnd();
83
- }
84
-
85
- function formatRunStatus(run: CIRunRecord): string {
86
- const lines: string[] = [];
87
- lines.push(`Run: ${run.id}`);
88
- lines.push(`Started: ${run.startTime}`);
89
-
90
- const endEvent = run.events.find((e): e is CIEndEvent => isCIEndEvent(e));
91
- if (endEvent) {
92
- lines.push(`Exit Code: ${endEvent.exit_code}`);
93
- lines.push(`Duration: ${(endEvent.duration_ms / 1000).toFixed(1)}s`);
94
-
95
- const status =
96
- endEvent.exit_code === 0
97
- ? "SUCCESS"
98
- : endEvent.exit_code === 10
99
- ? "BLOCKED"
100
- : endEvent.exit_code === 11
101
- ? "CANCELLED"
102
- : "ERROR";
103
- lines.push(`Status: ${status}`);
104
- } else {
105
- lines.push("Status: RUNNING");
106
- }
107
-
108
- return lines.join("\n");
109
- }
110
-
111
- /**
112
- * Create a CI run tracker that collects events and registers the run.
113
- */
114
- export function createRunTracker(runId: string): {
115
- collector: CIEventCollector;
116
- finalize: () => CIRunRecord;
117
- } {
118
- const collector = new CIEventCollector();
119
- const startTime = new Date().toISOString();
120
-
121
- return {
122
- collector,
123
- finalize() {
124
- const events = collector.all();
125
- const endEvent = events.find((e): e is CIEndEvent => isCIEndEvent(e));
126
- const record: CIRunRecord = {
127
- id: runId,
128
- startTime,
129
- events,
130
- exitCode: endEvent?.exit_code,
131
- durationMs: endEvent?.duration_ms,
132
- };
133
- registerRun(record);
134
- return record;
135
- },
136
- };
137
- }
package/src/types.ts DELETED
@@ -1,149 +0,0 @@
1
- /**
2
- * pi-ci — Shared types for headless CI mode.
3
- *
4
- * Exit code contract and CI event types per SPEC.md §2.1 and §5.
5
- */
6
-
7
- // ---------------------------------------------------------------------------
8
- // Exit codes
9
- // ---------------------------------------------------------------------------
10
-
11
- export const EXIT_CODES = {
12
- SUCCESS: 0,
13
- ERROR: 1,
14
- TIMEOUT: 1,
15
- BLOCKED: 10,
16
- CANCELLED: 11,
17
- NEEDS_INPUT: 12,
18
- } as const;
19
-
20
- export type ExitCode = (typeof EXIT_CODES)[keyof typeof EXIT_CODES];
21
-
22
- // ---------------------------------------------------------------------------
23
- // CI events (JSONL stream)
24
- // ---------------------------------------------------------------------------
25
-
26
- export type CIEventType =
27
- | "ci_start"
28
- | "ci_progress"
29
- | "ci_edit"
30
- | "ci_test"
31
- | "ci_cost"
32
- | "ci_end";
33
-
34
- export interface CIEventBase {
35
- type: CIEventType;
36
- timestamp: string; // ISO 8601
37
- }
38
-
39
- export interface CIStartEvent extends CIEventBase {
40
- type: "ci_start";
41
- task: string;
42
- mode: "single" | "plan";
43
- }
44
-
45
- export type CIRunPhase =
46
- | "exploring"
47
- | "planning"
48
- | "implementing"
49
- | "verifying"
50
- | "reviewing";
51
-
52
- export interface CIProgressEvent extends CIEventBase {
53
- type: "ci_progress";
54
- phase: CIRunPhase;
55
- files?: string[];
56
- }
57
-
58
- export interface CIEditEvent extends CIEventBase {
59
- type: "ci_edit";
60
- file: string;
61
- lines_added: number;
62
- lines_removed: number;
63
- }
64
-
65
- export interface CITestEvent extends CIEventBase {
66
- type: "ci_test";
67
- command: string;
68
- passed: number;
69
- failed: number;
70
- }
71
-
72
- export interface CICostEvent extends CIEventBase {
73
- type: "ci_cost";
74
- tokens: { input: number; output: number };
75
- cost_usd: number;
76
- }
77
-
78
- export interface CIEndEvent extends CIEventBase {
79
- type: "ci_end";
80
- exit_code: ExitCode;
81
- duration_ms: number;
82
- }
83
-
84
- export type CIEvent =
85
- | CIStartEvent
86
- | CIProgressEvent
87
- | CIEditEvent
88
- | CITestEvent
89
- | CICostEvent
90
- | CIEndEvent;
91
-
92
- // ---------------------------------------------------------------------------
93
- // Answer injection
94
- // ---------------------------------------------------------------------------
95
-
96
- export interface AnswerEntry {
97
- match: string;
98
- answer: string;
99
- }
100
-
101
- export interface AnswerFile {
102
- answers: AnswerEntry[];
103
- }
104
-
105
- // ---------------------------------------------------------------------------
106
- // Test results
107
- // ---------------------------------------------------------------------------
108
-
109
- export interface TestSummary {
110
- passed: number;
111
- failed: number;
112
- total: number;
113
- duration_ms: number;
114
- }
115
-
116
- // ---------------------------------------------------------------------------
117
- // PR creation
118
- // ---------------------------------------------------------------------------
119
-
120
- export interface PROptions {
121
- title: string;
122
- body?: string;
123
- base?: string;
124
- head?: string;
125
- draft?: boolean;
126
- labels?: string[];
127
- }
128
-
129
- export interface PRResult {
130
- url: string;
131
- number: number;
132
- }
133
-
134
- // ---------------------------------------------------------------------------
135
- // Pipeline
136
- // ---------------------------------------------------------------------------
137
-
138
- export type CIPipelineMode = "single" | "plan" | "review" | "supervised";
139
-
140
- export interface CIOptions {
141
- prompt: string;
142
- mode: CIPipelineMode;
143
- answersFile?: string;
144
- planFile?: string;
145
- prNumber?: number;
146
- resume?: string;
147
- idleTimeoutMs?: number;
148
- maxRetries?: number;
149
- }
package/tsconfig.json DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "strict": true,
7
- "noImplicitAny": true,
8
- "exactOptionalPropertyTypes": false,
9
- "skipLibCheck": true,
10
- "allowImportingTsExtensions": true,
11
- "noEmit": true,
12
- "types": ["node"]
13
- },
14
- "include": [
15
- "*.ts",
16
- "src/**/*.ts",
17
- "test/**/*.ts"
18
- ]
19
- }