@fiftth/fiftth-cli 1.0.1 → 1.1.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.
Files changed (116) hide show
  1. package/.fiftthnexus/.github/workflows/copilot-orchestrator.yml +78 -0
  2. package/.fiftthnexus/actions/Dockerfile +34 -0
  3. package/.fiftthnexus/actions/copilot-agent.mjs +269 -0
  4. package/.fiftthnexus/actions/package.json +8 -0
  5. package/.fiftthnexus/orchestrator.ts +2304 -0
  6. package/.fiftthnexus/skills/env-implement-prompt.md +65 -0
  7. package/.fiftthnexus/skills/env-plan-prompt.md +33 -0
  8. package/.fiftthnexus/skills/env-review-prompt.md +61 -0
  9. package/.fiftthnexus/skills/grill-me.md +9 -0
  10. package/.fiftthnexus/skills/prd-to-issues.md +150 -0
  11. package/.fiftthnexus/skills/write-prd.md +70 -0
  12. package/README.md +216 -25
  13. package/dist/api/client.d.ts +6 -0
  14. package/dist/api/client.d.ts.map +1 -1
  15. package/dist/api/client.js +13 -2
  16. package/dist/api/client.js.map +1 -1
  17. package/dist/commands/checkout.d.ts +6 -1
  18. package/dist/commands/checkout.d.ts.map +1 -1
  19. package/dist/commands/checkout.js +415 -44
  20. package/dist/commands/checkout.js.map +1 -1
  21. package/dist/commands/login.d.ts +0 -2
  22. package/dist/commands/login.d.ts.map +1 -1
  23. package/dist/commands/login.js +83 -32
  24. package/dist/commands/login.js.map +1 -1
  25. package/dist/commands/model.d.ts +2 -0
  26. package/dist/commands/model.d.ts.map +1 -0
  27. package/dist/commands/model.js +32 -0
  28. package/dist/commands/model.js.map +1 -0
  29. package/dist/commands/planningContext.d.ts +6 -0
  30. package/dist/commands/planningContext.d.ts.map +1 -0
  31. package/dist/commands/planningContext.js +91 -0
  32. package/dist/commands/planningContext.js.map +1 -0
  33. package/dist/commands/repo.d.ts.map +1 -1
  34. package/dist/commands/repo.js +38 -15
  35. package/dist/commands/repo.js.map +1 -1
  36. package/dist/commands/skills.d.ts +2 -0
  37. package/dist/commands/skills.d.ts.map +1 -0
  38. package/dist/commands/skills.js +123 -0
  39. package/dist/commands/skills.js.map +1 -0
  40. package/dist/commands/use.d.ts +1 -5
  41. package/dist/commands/use.d.ts.map +1 -1
  42. package/dist/commands/use.js +63 -48
  43. package/dist/commands/use.js.map +1 -1
  44. package/dist/index.js +86 -27
  45. package/dist/index.js.map +1 -1
  46. package/dist/services/nexusService.d.ts +30 -0
  47. package/dist/services/nexusService.d.ts.map +1 -0
  48. package/dist/services/nexusService.js +188 -0
  49. package/dist/services/nexusService.js.map +1 -0
  50. package/dist/services/prdService.d.ts +12 -0
  51. package/dist/services/prdService.d.ts.map +1 -0
  52. package/dist/services/prdService.js +103 -0
  53. package/dist/services/prdService.js.map +1 -0
  54. package/dist/services/taskSelection.d.ts +10 -0
  55. package/dist/services/taskSelection.d.ts.map +1 -0
  56. package/dist/services/taskSelection.js +112 -0
  57. package/dist/services/taskSelection.js.map +1 -0
  58. package/dist/services/taskService.d.ts +23 -1
  59. package/dist/services/taskService.d.ts.map +1 -1
  60. package/dist/services/taskService.js +118 -12
  61. package/dist/services/taskService.js.map +1 -1
  62. package/dist/utils/config.d.ts +2 -0
  63. package/dist/utils/config.d.ts.map +1 -1
  64. package/dist/utils/config.js +20 -3
  65. package/dist/utils/config.js.map +1 -1
  66. package/dist/utils/dashboard.d.ts +65 -0
  67. package/dist/utils/dashboard.d.ts.map +1 -0
  68. package/dist/utils/dashboard.js +205 -0
  69. package/dist/utils/dashboard.js.map +1 -0
  70. package/dist/utils/models.d.ts +14 -0
  71. package/dist/utils/models.d.ts.map +1 -0
  72. package/dist/utils/models.js +89 -0
  73. package/dist/utils/models.js.map +1 -0
  74. package/dist/utils/ui.d.ts +6 -0
  75. package/dist/utils/ui.d.ts.map +1 -1
  76. package/dist/utils/ui.js +22 -1
  77. package/dist/utils/ui.js.map +1 -1
  78. package/dist/utils/version.d.ts +4 -0
  79. package/dist/utils/version.d.ts.map +1 -0
  80. package/dist/utils/version.js +26 -0
  81. package/dist/utils/version.js.map +1 -0
  82. package/package.json +9 -4
  83. package/.github/workflows/publish-npm.yml +0 -62
  84. package/dist/commands/tasks.d.ts +0 -2
  85. package/dist/commands/tasks.d.ts.map +0 -1
  86. package/dist/commands/tasks.js +0 -69
  87. package/dist/commands/tasks.js.map +0 -1
  88. package/dist/context/runtimeContext.d.ts +0 -14
  89. package/dist/context/runtimeContext.d.ts.map +0 -1
  90. package/dist/context/runtimeContext.js +0 -21
  91. package/dist/context/runtimeContext.js.map +0 -1
  92. package/dist/services/taskContext.d.ts +0 -14
  93. package/dist/services/taskContext.d.ts.map +0 -1
  94. package/dist/services/taskContext.js +0 -15
  95. package/dist/services/taskContext.js.map +0 -1
  96. package/dist/utils/api.d.ts +0 -10
  97. package/dist/utils/api.d.ts.map +0 -1
  98. package/dist/utils/api.js +0 -25
  99. package/dist/utils/api.js.map +0 -1
  100. package/src/api/client.ts +0 -31
  101. package/src/commands/checkout.ts +0 -101
  102. package/src/commands/login.ts +0 -145
  103. package/src/commands/repo.ts +0 -113
  104. package/src/commands/tasks.ts +0 -86
  105. package/src/commands/use.ts +0 -149
  106. package/src/config/configService.ts +0 -56
  107. package/src/context/runtimeContext.ts +0 -42
  108. package/src/git/gitService.ts +0 -29
  109. package/src/index.ts +0 -133
  110. package/src/services/taskContext.ts +0 -32
  111. package/src/services/taskService.ts +0 -53
  112. package/src/utils/api.ts +0 -41
  113. package/src/utils/config.ts +0 -48
  114. package/src/utils/ui.ts +0 -46
  115. package/tsconfig.json +0 -18
  116. package/vitest.config.ts +0 -8
@@ -0,0 +1,78 @@
1
+ name: Copilot Parallel Orchestrator
2
+
3
+ on:
4
+ # Run manually
5
+ workflow_dispatch:
6
+ inputs:
7
+ max_parallel:
8
+ description: "Max issues in parallel"
9
+ default: "3"
10
+ type: string
11
+ max_iterations:
12
+ description: "Max orchestrator iterations"
13
+ default: "10"
14
+ type: string
15
+ label:
16
+ description: "Label to filter issues"
17
+ default: "ai-running-orchestrator"
18
+ type: string
19
+
20
+ # Run when an issue gets the ai-running-orchestrator label
21
+ issues:
22
+ types: [labeled]
23
+
24
+ # Poll every 30 minutes for newly unblocked issues
25
+ schedule:
26
+ - cron: "*/30 * * * *"
27
+
28
+ permissions:
29
+ contents: write
30
+ issues: write
31
+ pull-requests: write
32
+
33
+ # Only one orchestrator at a time
34
+ concurrency:
35
+ group: copilot-orchestrator
36
+ cancel-in-progress: false
37
+
38
+ jobs:
39
+ orchestrate:
40
+ # Skip if triggered by label event and label is not ai-running-orchestrator
41
+ if: >
42
+ github.event_name != 'issues' || github.event.label.name == 'ai-running-orchestrator'
43
+ runs-on: ubuntu-latest
44
+ timeout-minutes: 120
45
+
46
+ steps:
47
+ - name: Checkout
48
+ uses: actions/checkout@v4
49
+
50
+ - name: Setup Node.js
51
+ uses: actions/setup-node@v4
52
+ with:
53
+ node-version: "22"
54
+
55
+ - name: Install tsx
56
+ run: npm install -g tsx
57
+
58
+ - name: Run Orchestrator
59
+ env:
60
+ GH_TOKEN: "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"
61
+ GITHUB_REPOSITORY: ${{ github.repository }}
62
+ MAX_PARALLEL: ${{ inputs.max_parallel || '3' }}
63
+ MAX_ITERATIONS: ${{ inputs.max_iterations || '10' }}
64
+ ORCHESTRATOR_LABEL: ${{ inputs.label || 'ai-running-orchestrator' }}
65
+ POLL_INTERVAL_MS: "60000"
66
+ MAX_WAIT_MS: "3600000"
67
+ run: npx tsx custom-agents/actions/orchestrator.ts
68
+
69
+ - name: Write Summary
70
+ if: always()
71
+ run: |
72
+ echo "## Copilot Orchestrator Run" >> $GITHUB_STEP_SUMMARY
73
+ echo "" >> $GITHUB_STEP_SUMMARY
74
+ echo "- **Trigger:** ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY
75
+ echo "- **Max parallel:** ${{ inputs.max_parallel || '3' }}" >> $GITHUB_STEP_SUMMARY
76
+ echo "- **Label:** ${{ inputs.label || 'ai-running-orchestrator' }}" >> $GITHUB_STEP_SUMMARY
77
+ echo "" >> $GITHUB_STEP_SUMMARY
78
+ echo "Check the job logs for detailed output." >> $GITHUB_STEP_SUMMARY
@@ -0,0 +1,34 @@
1
+ FROM node:22-slim
2
+
3
+ RUN apt-get update && apt-get install -y --no-install-recommends \
4
+ git \
5
+ curl \
6
+ ca-certificates \
7
+ jq \
8
+ && rm -rf /var/lib/apt/lists/*
9
+
10
+ # Install GitHub CLI
11
+ RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
12
+ -o /usr/share/keyrings/githubcli-archive-keyring.gpg \
13
+ && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
14
+ > /etc/apt/sources.list.d/github-cli.list \
15
+ && apt-get update \
16
+ && apt-get install -y gh \
17
+ && rm -rf /var/lib/apt/lists/*
18
+
19
+ # Install Copilot CLI (required by @github/copilot-sdk)
20
+ RUN npm install -g @github/copilot@latest
21
+
22
+ RUN git config --global user.name "copilot-orchestrator" \
23
+ && git config --global user.email "copilot-orchestrator@local" \
24
+ && git config --global init.defaultBranch main
25
+
26
+ # Install SDK dependencies
27
+ COPY package.json /opt/package.json
28
+ RUN cd /opt && npm install --omit=dev
29
+
30
+ COPY copilot-agent.mjs /opt/copilot-agent.mjs
31
+
32
+ WORKDIR /workspace
33
+
34
+ ENTRYPOINT ["node", "/opt/copilot-agent.mjs"]
@@ -0,0 +1,269 @@
1
+ import { readFileSync } from "fs";
2
+ import { approveAll, CopilotClient } from "@github/copilot-sdk";
3
+
4
+ const STAGE_MARKER = "__FIFTTH_AGENT_STAGE__";
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // CLI Args
8
+ // ---------------------------------------------------------------------------
9
+ function parseArgs() {
10
+ const args = process.argv.slice(2);
11
+ const defaultModel = process.env.FIFTTH_DEFAULT_MODEL ?? "gpt-5.4";
12
+ const opts = {
13
+ promptFile: "",
14
+ planPromptFile: "",
15
+ implementPromptFile: "",
16
+ reviewPromptFile: "",
17
+ stage: "plan",
18
+ model: defaultModel,
19
+ verbose: false,
20
+ };
21
+
22
+ for (let i = 0; i < args.length; i++) {
23
+ switch (args[i]) {
24
+ case "--prompt":
25
+ opts.promptFile = args[++i] ?? "";
26
+ break;
27
+ case "--plan-prompt":
28
+ opts.planPromptFile = args[++i] ?? "";
29
+ break;
30
+ case "--implement-prompt":
31
+ opts.implementPromptFile = args[++i] ?? "";
32
+ break;
33
+ case "--review-prompt":
34
+ opts.reviewPromptFile = args[++i] ?? "";
35
+ break;
36
+ case "--stage":
37
+ opts.stage = args[++i] ?? "plan";
38
+ break;
39
+ case "--model":
40
+ opts.model = args[++i] ?? defaultModel;
41
+ break;
42
+ case "--verbose":
43
+ opts.verbose = true;
44
+ break;
45
+ }
46
+ }
47
+
48
+ const isSingleStage = Boolean(opts.promptFile);
49
+ const isPipeline = Boolean(opts.planPromptFile && opts.implementPromptFile && opts.reviewPromptFile);
50
+
51
+ if (!isSingleStage && !isPipeline) {
52
+ console.error("ERROR: provide --prompt <file> or the full pipeline prompt set (--plan-prompt, --implement-prompt, --review-prompt)");
53
+ process.exit(1);
54
+ }
55
+ return opts;
56
+ }
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // Logging
60
+ // ---------------------------------------------------------------------------
61
+ function log(msg) {
62
+ process.stderr.write(`[agent] ${msg}\n`);
63
+ }
64
+
65
+ function emitStageEvent(stage, event) {
66
+ log(`${STAGE_MARKER} ${stage}:${event}`);
67
+ }
68
+
69
+ function applyPromptContext(template, replacements) {
70
+ let result = template;
71
+ for (const [key, value] of Object.entries(replacements)) {
72
+ result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
73
+ }
74
+ return result;
75
+ }
76
+
77
+ async function finishPipeline(client, pipelineResult, exitCode = 0) {
78
+ process.stdout.write(JSON.stringify(pipelineResult));
79
+ await client.stop();
80
+ if (exitCode !== 0) {
81
+ process.exit(exitCode);
82
+ }
83
+ }
84
+
85
+
86
+ const SYSTEM_PROMPTS = {
87
+ plan:
88
+ `You are a senior software architect. You have tools to explore the codebase.
89
+ Use them to understand the project structure before producing your plan.
90
+ After exploring, output your plan as a JSON object wrapped in <plan> tags.`,
91
+
92
+ implement:
93
+ `You are a senior full-stack developer implementing features in an existing codebase.
94
+ You have tools to explore, read, search, and write files, plus run shell commands.
95
+
96
+ WORKFLOW:
97
+ 1. EXPLORE: Use tools to understand the existing codebase, tech stack, patterns, and conventions.
98
+ 2. IMPLEMENT: Create/update files. Match the existing stack and style EXACTLY.
99
+ 3. COMMIT: git add and git commit your changes.
100
+
101
+ RULES:
102
+ - ALWAYS explore relevant existing files before writing new ones
103
+ - Match existing patterns, imports, and conventions exactly
104
+ - Do NOT introduce frameworks or libraries not already in package.json
105
+ - Write complete file contents (not diffs or partial content)
106
+ - Run validation after writing code`,
107
+
108
+ review:
109
+ `You are a senior code reviewer performing a lightweight final pass on an already-implemented change.
110
+ You have tools to inspect the diff, read nearby code, run focused validation, and make small follow-up fixes.
111
+
112
+ WORKFLOW:
113
+ 1. Inspect only the current diff and directly affected code paths.
114
+ 2. Run only targeted validation for the changed paths.
115
+ 3. If you find one concrete correctness or test gap, fix it and commit once.
116
+ 4. If there is no concrete blocker, stop immediately and output COMPLETE.
117
+
118
+ Avoid broad refactors, exhaustive analysis, or reopening implementation work that already passed.`,
119
+
120
+ merge: `You are a senior developer responsible for merging branches.
121
+ You have tools to run git commands and resolve conflicts.
122
+ Follow the merge instructions carefully.`,
123
+ };
124
+
125
+ async function runStage(client, stage, prompt, model, verbose) {
126
+ emitStageEvent(stage, "start");
127
+ const systemPrompt = SYSTEM_PROMPTS[stage] ?? SYSTEM_PROMPTS.plan;
128
+ const session = await client.createSession({
129
+ model,
130
+ systemMessage: {
131
+ mode: "replace",
132
+ content: systemPrompt,
133
+ },
134
+ onPermissionRequest: approveAll,
135
+ infiniteSessions: { enabled: true },
136
+ });
137
+
138
+ let toolCalls = 0;
139
+ let writeCount = 0;
140
+
141
+ session.on("tool.execution_start", (event) => {
142
+ toolCalls++;
143
+ if (verbose) {
144
+ log(` tool(${stage}): ${event.data?.toolName ?? "unknown"}`);
145
+ }
146
+ });
147
+
148
+ session.on("tool.execution_complete", (event) => {
149
+ if (
150
+ event.data?.toolName === "write_file" ||
151
+ event.data?.toolName === "edit_file"
152
+ ) {
153
+ writeCount++;
154
+ }
155
+ });
156
+
157
+ session.on("assistant.message_delta", (event) => {
158
+ if (verbose && event.data?.deltaContent) {
159
+ process.stderr.write(event.data.deltaContent);
160
+ }
161
+ });
162
+
163
+ try {
164
+ log(`Sending ${stage} prompt...`);
165
+ const response = await session.sendAndWait(
166
+ { prompt },
167
+ 10 * 60 * 1000,
168
+ );
169
+
170
+ const output = response?.data?.content ?? "";
171
+ log(`${stage} done (${toolCalls} tool calls, ${writeCount} writes, ${output.length} chars)`);
172
+ emitStageEvent(stage, "done");
173
+ await session.disconnect();
174
+ return {
175
+ success: true,
176
+ output,
177
+ toolCalls,
178
+ writes: writeCount,
179
+ errorText: "",
180
+ };
181
+ } catch (err) {
182
+ const errorText = err instanceof Error ? err.message : String(err);
183
+ emitStageEvent(stage, "failed");
184
+ await session.disconnect().catch(() => {});
185
+ return {
186
+ success: false,
187
+ output: "",
188
+ toolCalls,
189
+ writes: writeCount,
190
+ errorText,
191
+ };
192
+ }
193
+ }
194
+
195
+ // ---------------------------------------------------------------------------
196
+ // Main
197
+ // ---------------------------------------------------------------------------
198
+ async function main() {
199
+ const opts = parseArgs();
200
+
201
+ log(`stage=${opts.stage} model=${opts.model} verbose=${opts.verbose}`);
202
+
203
+ log("Starting Copilot SDK client...");
204
+ const client = new CopilotClient({
205
+ githubToken: process.env.GITHUB_TOKEN,
206
+ });
207
+ await client.start();
208
+
209
+ try {
210
+ if (opts.promptFile) {
211
+ const prompt = readFileSync(opts.promptFile, "utf-8");
212
+ log(`Prompt ready (${prompt.length} chars)`);
213
+ const result = await runStage(client, opts.stage, prompt, opts.model, opts.verbose);
214
+ process.stdout.write(result.output);
215
+ await client.stop();
216
+ if (!result.success) {
217
+ process.exit(1);
218
+ }
219
+ return;
220
+ }
221
+
222
+ const planPrompt = readFileSync(opts.planPromptFile, "utf-8");
223
+ const implementPromptTemplate = readFileSync(opts.implementPromptFile, "utf-8");
224
+ const reviewPromptTemplate = readFileSync(opts.reviewPromptFile, "utf-8");
225
+ log(`Pipeline prompts ready (${planPrompt.length}/${implementPromptTemplate.length}/${reviewPromptTemplate.length} chars)`);
226
+
227
+ const pipelineResult = {
228
+ plan: await runStage(client, "plan", planPrompt, opts.model, opts.verbose),
229
+ };
230
+
231
+ if (!pipelineResult.plan.success) {
232
+ await finishPipeline(client, pipelineResult, 1);
233
+ }
234
+
235
+ pipelineResult.implement = await runStage(
236
+ client,
237
+ "implement",
238
+ applyPromptContext(implementPromptTemplate, {
239
+ PLAN_OUTPUT: pipelineResult.plan.output,
240
+ }),
241
+ opts.model,
242
+ opts.verbose,
243
+ );
244
+
245
+ if (!pipelineResult.implement.success) {
246
+ await finishPipeline(client, pipelineResult, 1);
247
+ }
248
+
249
+ pipelineResult.review = await runStage(
250
+ client,
251
+ "review",
252
+ applyPromptContext(reviewPromptTemplate, {
253
+ IMPLEMENTATION_OUTPUT: pipelineResult.implement.output,
254
+ }),
255
+ opts.model,
256
+ opts.verbose,
257
+ );
258
+
259
+ await finishPipeline(client, pipelineResult, pipelineResult.review.success ? 0 : 1);
260
+ } catch (err) {
261
+ await client.forceStop().catch(() => {});
262
+ throw err;
263
+ }
264
+ }
265
+
266
+ main().catch((err) => {
267
+ console.error(`[agent] FATAL: ${err.message}`);
268
+ process.exit(1);
269
+ });
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "copilot-agent",
3
+ "private": true,
4
+ "type": "module",
5
+ "dependencies": {
6
+ "@github/copilot-sdk": "^0.2.0"
7
+ }
8
+ }