@supaku/agentfactory-linear 0.7.9 → 0.7.10
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/dist/src/constants.d.ts +5 -0
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +7 -0
- package/dist/src/defaults/index.d.ts +1 -1
- package/dist/src/defaults/index.d.ts.map +1 -1
- package/dist/src/defaults/index.js +1 -1
- package/dist/src/defaults/prompts.d.ts +18 -1
- package/dist/src/defaults/prompts.d.ts.map +1 -1
- package/dist/src/defaults/prompts.js +108 -5
- package/dist/src/defaults/prompts.test.d.ts +2 -0
- package/dist/src/defaults/prompts.test.d.ts.map +1 -0
- package/dist/src/defaults/prompts.test.js +130 -0
- package/dist/src/frontend-adapter.d.ts +168 -0
- package/dist/src/frontend-adapter.d.ts.map +1 -0
- package/dist/src/frontend-adapter.js +314 -0
- package/dist/src/frontend-adapter.test.d.ts +2 -0
- package/dist/src/frontend-adapter.test.d.ts.map +1 -0
- package/dist/src/frontend-adapter.test.js +545 -0
- package/dist/src/index.d.ts +4 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -2
- package/package.json +1 -1
package/dist/src/constants.d.ts
CHANGED
|
@@ -34,6 +34,11 @@ export declare const CONTINUATION_MARKER = "\n\n*...continued in next comment*";
|
|
|
34
34
|
* hoisting issues where the value is captured before dotenv runs.
|
|
35
35
|
*/
|
|
36
36
|
export declare function getDefaultTeamId(): string;
|
|
37
|
+
/**
|
|
38
|
+
* Default team name
|
|
39
|
+
* Can be set via LINEAR_TEAM_NAME env var (auto-set by orchestrator from issue context)
|
|
40
|
+
*/
|
|
41
|
+
export declare function getDefaultTeamName(): string;
|
|
37
42
|
/**
|
|
38
43
|
* Project IDs — must be set via env vars:
|
|
39
44
|
* - LINEAR_PROJECT_AGENT
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,QAAQ,CAAA;AAE9C;;GAEG;AACH,eAAO,MAAM,iBAAiB,gCAAgC,CAAA;AAE9D;;GAEG;AACH,eAAO,MAAM,uBAAuB,KAAK,CAAA;AAEzC;;GAEG;AACH,eAAO,MAAM,gBAAgB,MAAM,CAAA;AAEnC;;GAEG;AACH,eAAO,MAAM,mBAAmB,uCAAuC,CAAA;AAQvE;;;;;;GAMG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED;;;;;GAKG;AACH,eAAO,MAAM,eAAe;;;IAG1B,mDAAmD;;CAE3C,CAAA;AAEV;;;;;;GAMG;AACH,eAAO,MAAM,aAAa;;;;;CAKhB,CAAA;AAGV,eAAO,MAAM,gBAAgB;IAC3B,+DAA+D;;IAE/D,2CAA2C;;CAEnC,CAAA;AAMV;;GAEG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;CAS1B,CAAA;AAEV,MAAM,MAAM,oBAAoB,GAC9B,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,OAAO,uBAAuB,CAAC,CAAA"}
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,QAAQ,CAAA;AAE9C;;GAEG;AACH,eAAO,MAAM,iBAAiB,gCAAgC,CAAA;AAE9D;;GAEG;AACH,eAAO,MAAM,uBAAuB,KAAK,CAAA;AAEzC;;GAEG;AACH,eAAO,MAAM,gBAAgB,MAAM,CAAA;AAEnC;;GAEG;AACH,eAAO,MAAM,mBAAmB,uCAAuC,CAAA;AAQvE;;;;;;GAMG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED;;;;;GAKG;AACH,eAAO,MAAM,eAAe;;;IAG1B,mDAAmD;;CAE3C,CAAA;AAEV;;;;;;GAMG;AACH,eAAO,MAAM,aAAa;;;;;CAKhB,CAAA;AAGV,eAAO,MAAM,gBAAgB;IAC3B,+DAA+D;;IAE/D,2CAA2C;;CAEnC,CAAA;AAMV;;GAEG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;CAS1B,CAAA;AAEV,MAAM,MAAM,oBAAoB,GAC9B,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,OAAO,uBAAuB,CAAC,CAAA"}
|
package/dist/src/constants.js
CHANGED
|
@@ -44,6 +44,13 @@ export const CONTINUATION_MARKER = '\n\n*...continued in next comment*';
|
|
|
44
44
|
export function getDefaultTeamId() {
|
|
45
45
|
return process.env.LINEAR_TEAM_ID ?? '';
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Default team name
|
|
49
|
+
* Can be set via LINEAR_TEAM_NAME env var (auto-set by orchestrator from issue context)
|
|
50
|
+
*/
|
|
51
|
+
export function getDefaultTeamName() {
|
|
52
|
+
return process.env.LINEAR_TEAM_NAME ?? '';
|
|
53
|
+
}
|
|
47
54
|
/**
|
|
48
55
|
* Project IDs — must be set via env vars:
|
|
49
56
|
* - LINEAR_PROJECT_AGENT
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* New users start with these defaults and customize as needed.
|
|
6
6
|
* Supaku overrides these with its own prompts.ts.
|
|
7
7
|
*/
|
|
8
|
-
export { defaultGeneratePrompt, defaultBuildParentQAContext, defaultBuildParentAcceptanceContext, } from './prompts.js';
|
|
8
|
+
export { defaultGeneratePrompt, defaultBuildParentQAContext, defaultBuildParentAcceptanceContext, buildFailureContextBlock, WORK_RESULT_MARKER_INSTRUCTION, PR_SELECTION_GUIDANCE, type WorkflowContext, } from './prompts.js';
|
|
9
9
|
export { defaultDetectWorkTypeFromPrompt } from './work-type-detection.js';
|
|
10
10
|
export { defaultGetPriority } from './priority.js';
|
|
11
11
|
export { defaultParseAutoTriggerConfig, type DefaultAutoTriggerConfig, } from './auto-trigger.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/defaults/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,qBAAqB,EACrB,2BAA2B,EAC3B,mCAAmC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/defaults/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,qBAAqB,EACrB,2BAA2B,EAC3B,mCAAmC,EACnC,wBAAwB,EACxB,8BAA8B,EAC9B,qBAAqB,EACrB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAA;AAErB,OAAO,EAAE,+BAA+B,EAAE,MAAM,0BAA0B,CAAA;AAE1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAElD,OAAO,EACL,6BAA6B,EAC7B,KAAK,wBAAwB,GAC9B,MAAM,mBAAmB,CAAA"}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* New users start with these defaults and customize as needed.
|
|
6
6
|
* Supaku overrides these with its own prompts.ts.
|
|
7
7
|
*/
|
|
8
|
-
export { defaultGeneratePrompt, defaultBuildParentQAContext, defaultBuildParentAcceptanceContext, } from './prompts.js';
|
|
8
|
+
export { defaultGeneratePrompt, defaultBuildParentQAContext, defaultBuildParentAcceptanceContext, buildFailureContextBlock, WORK_RESULT_MARKER_INSTRUCTION, PR_SELECTION_GUIDANCE, } from './prompts.js';
|
|
9
9
|
export { defaultDetectWorkTypeFromPrompt } from './work-type-detection.js';
|
|
10
10
|
export { defaultGetPriority } from './priority.js';
|
|
11
11
|
export { defaultParseAutoTriggerConfig, } from './auto-trigger.js';
|
|
@@ -5,14 +5,31 @@
|
|
|
5
5
|
* The Supaku project overrides these with its own prompts.ts.
|
|
6
6
|
*/
|
|
7
7
|
import type { AgentWorkType, SubIssueStatus } from '../types.js';
|
|
8
|
+
export declare const WORK_RESULT_MARKER_INSTRUCTION = "\n\nMANDATORY \u2014 Structured Result Marker:\nYou MUST include exactly one of these HTML comment markers in your final output:\n- On pass: <!-- WORK_RESULT:passed -->\n- On fail: <!-- WORK_RESULT:failed -->\nWithout this marker, the orchestrator CANNOT detect your result and the issue status will NOT be updated. Even if you encounter errors, always emit <!-- WORK_RESULT:failed -->.";
|
|
9
|
+
export declare const PR_SELECTION_GUIDANCE = "\n\nPR Selection (Multi-PR Handling):\nIssues may have multiple PRs. Select the correct one:\n1. Check linked PRs in the issue attachments/links for GitHub PR URLs\n2. Filter by state \u2014 prefer OPEN over MERGED over CLOSED: gh pr view NNN --json state\n3. If multiple OPEN PRs, pick the most recently created one\n4. Fallback search by branch: gh pr list --head \"$(git branch --show-current)\" --state open\n5. Last resort search by issue ID: gh pr list --state open --search \"[issue-id]\"\n6. If no PR found, emit WORK_RESULT:failed with explanation";
|
|
10
|
+
/**
|
|
11
|
+
* Context from the workflow state machine for retry enrichment.
|
|
12
|
+
* Injected when an issue has been through previous dev-QA-rejected cycles.
|
|
13
|
+
*/
|
|
14
|
+
export interface WorkflowContext {
|
|
15
|
+
cycleCount: number;
|
|
16
|
+
strategy: string;
|
|
17
|
+
failureSummary: string | null;
|
|
18
|
+
qaAttemptCount?: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Build the failure context block to append to prompts for retries.
|
|
22
|
+
*/
|
|
23
|
+
export declare function buildFailureContextBlock(workType: AgentWorkType, context: WorkflowContext): string;
|
|
8
24
|
/**
|
|
9
25
|
* Generate a default prompt for a given work type and issue identifier.
|
|
10
26
|
*
|
|
11
27
|
* @param identifier - The issue identifier (e.g., "PROJ-123")
|
|
12
28
|
* @param workType - The type of work to perform
|
|
13
29
|
* @param mentionContext - Optional additional context from a user mention
|
|
30
|
+
* @param workflowContext - Optional workflow state context for retry enrichment
|
|
14
31
|
*/
|
|
15
|
-
export declare function defaultGeneratePrompt(identifier: string, workType: AgentWorkType, mentionContext?: string): string;
|
|
32
|
+
export declare function defaultGeneratePrompt(identifier: string, workType: AgentWorkType, mentionContext?: string, workflowContext?: WorkflowContext): string;
|
|
16
33
|
/**
|
|
17
34
|
* Build default QA context for parent issues with sub-issues.
|
|
18
35
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../../src/defaults/prompts.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAahE
|
|
1
|
+
{"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../../src/defaults/prompts.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAahE,eAAO,MAAM,8BAA8B,uYAMsI,CAAA;AAEjL,eAAO,MAAM,qBAAqB,ijBAS0B,CAAA;AAE5D;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,aAAa,EACvB,OAAO,EAAE,eAAe,GACvB,MAAM,CA0DR;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,aAAa,EACvB,cAAc,CAAC,EAAE,MAAM,EACvB,eAAe,CAAC,EAAE,eAAe,GAChC,MAAM,CAuFR;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CACzC,eAAe,EAAE,MAAM,EACvB,gBAAgB,EAAE,cAAc,EAAE,GACjC,MAAM,CAoBR;AAED;;GAEG;AACH,wBAAgB,mCAAmC,CACjD,eAAe,EAAE,MAAM,EACvB,gBAAgB,EAAE,cAAc,EAAE,GACjC,MAAM,CAmBR"}
|
|
@@ -14,14 +14,91 @@ manual setup steps, policy decisions, access permissions), create a blocker issu
|
|
|
14
14
|
This creates a tracked issue in Icebox with 'Needs Human' label, linked as blocking the source issue.
|
|
15
15
|
Do NOT silently skip human-needed work or bury it in comments.
|
|
16
16
|
Only create blockers for things that genuinely require a human — not for things you can retry or work around.`;
|
|
17
|
+
export const WORK_RESULT_MARKER_INSTRUCTION = `
|
|
18
|
+
|
|
19
|
+
MANDATORY — Structured Result Marker:
|
|
20
|
+
You MUST include exactly one of these HTML comment markers in your final output:
|
|
21
|
+
- On pass: <!-- WORK_RESULT:passed -->
|
|
22
|
+
- On fail: <!-- WORK_RESULT:failed -->
|
|
23
|
+
Without this marker, the orchestrator CANNOT detect your result and the issue status will NOT be updated. Even if you encounter errors, always emit <!-- WORK_RESULT:failed -->.`;
|
|
24
|
+
export const PR_SELECTION_GUIDANCE = `
|
|
25
|
+
|
|
26
|
+
PR Selection (Multi-PR Handling):
|
|
27
|
+
Issues may have multiple PRs. Select the correct one:
|
|
28
|
+
1. Check linked PRs in the issue attachments/links for GitHub PR URLs
|
|
29
|
+
2. Filter by state — prefer OPEN over MERGED over CLOSED: gh pr view NNN --json state
|
|
30
|
+
3. If multiple OPEN PRs, pick the most recently created one
|
|
31
|
+
4. Fallback search by branch: gh pr list --head "$(git branch --show-current)" --state open
|
|
32
|
+
5. Last resort search by issue ID: gh pr list --state open --search "[issue-id]"
|
|
33
|
+
6. If no PR found, emit WORK_RESULT:failed with explanation`;
|
|
34
|
+
/**
|
|
35
|
+
* Build the failure context block to append to prompts for retries.
|
|
36
|
+
*/
|
|
37
|
+
export function buildFailureContextBlock(workType, context) {
|
|
38
|
+
if (context.cycleCount <= 0)
|
|
39
|
+
return '';
|
|
40
|
+
switch (workType) {
|
|
41
|
+
case 'refinement': {
|
|
42
|
+
if (context.strategy === 'decompose') {
|
|
43
|
+
return `\n\n## Decomposition Required (Cycle ${context.cycleCount})
|
|
44
|
+
|
|
45
|
+
This issue has been through ${context.cycleCount} development-QA cycle(s) and keeps failing.
|
|
46
|
+
Instead of refining, DECOMPOSE this issue into smaller, independently testable pieces.
|
|
47
|
+
|
|
48
|
+
### Failure History
|
|
49
|
+
${context.failureSummary ?? 'No details recorded.'}
|
|
50
|
+
|
|
51
|
+
### Decomposition Instructions
|
|
52
|
+
- Break this issue into smaller sub-issues (use --parentId to make them children)
|
|
53
|
+
- Each sub-issue must have clear, unambiguous acceptance criteria
|
|
54
|
+
- Each sub-issue must be testable in isolation
|
|
55
|
+
- Address one specific concern that previous attempts failed on
|
|
56
|
+
- After creating sub-issues, move the PARENT issue to Backlog status`;
|
|
57
|
+
}
|
|
58
|
+
return `\n\n## Previous Failure Context
|
|
59
|
+
|
|
60
|
+
This issue has been through ${context.cycleCount} development-QA cycle(s).
|
|
61
|
+
|
|
62
|
+
### Failure History
|
|
63
|
+
${context.failureSummary ?? 'No details recorded.'}
|
|
64
|
+
|
|
65
|
+
### Instructions
|
|
66
|
+
- Read the failure history carefully before making changes
|
|
67
|
+
- Do NOT repeat approaches that already failed
|
|
68
|
+
- If the acceptance criteria are ambiguous, update them to be testable before fixing code
|
|
69
|
+
- Focus on the ROOT CAUSE, not symptoms`;
|
|
70
|
+
}
|
|
71
|
+
case 'development':
|
|
72
|
+
case 'coordination': {
|
|
73
|
+
return `\n\n## Retry Context
|
|
74
|
+
|
|
75
|
+
This is retry #${context.cycleCount} for this issue. Previous QA failures:
|
|
76
|
+
${context.failureSummary ?? 'No details recorded.'}
|
|
77
|
+
|
|
78
|
+
Pay special attention to the areas that failed QA previously.`;
|
|
79
|
+
}
|
|
80
|
+
case 'qa':
|
|
81
|
+
case 'qa-coordination': {
|
|
82
|
+
if (!context.failureSummary)
|
|
83
|
+
return '';
|
|
84
|
+
return `\n\n## Previous QA Results
|
|
85
|
+
This issue has been QA'd ${context.qaAttemptCount ?? context.cycleCount} times previously.
|
|
86
|
+
${context.failureSummary}
|
|
87
|
+
Focus validation on these previously failing areas.`;
|
|
88
|
+
}
|
|
89
|
+
default:
|
|
90
|
+
return '';
|
|
91
|
+
}
|
|
92
|
+
}
|
|
17
93
|
/**
|
|
18
94
|
* Generate a default prompt for a given work type and issue identifier.
|
|
19
95
|
*
|
|
20
96
|
* @param identifier - The issue identifier (e.g., "PROJ-123")
|
|
21
97
|
* @param workType - The type of work to perform
|
|
22
98
|
* @param mentionContext - Optional additional context from a user mention
|
|
99
|
+
* @param workflowContext - Optional workflow state context for retry enrichment
|
|
23
100
|
*/
|
|
24
|
-
export function defaultGeneratePrompt(identifier, workType, mentionContext) {
|
|
101
|
+
export function defaultGeneratePrompt(identifier, workType, mentionContext, workflowContext) {
|
|
25
102
|
let basePrompt;
|
|
26
103
|
switch (workType) {
|
|
27
104
|
case 'research':
|
|
@@ -45,10 +122,30 @@ Do NOT wait for user approval - create issues automatically.`;
|
|
|
45
122
|
basePrompt = `Continue work on ${identifier}. Resume where you left off.`;
|
|
46
123
|
break;
|
|
47
124
|
case 'qa':
|
|
48
|
-
basePrompt = `QA ${identifier}. Validate the implementation against acceptance criteria
|
|
125
|
+
basePrompt = `QA ${identifier}. Validate the implementation against acceptance criteria.
|
|
126
|
+
${WORK_RESULT_MARKER_INSTRUCTION}
|
|
127
|
+
${PR_SELECTION_GUIDANCE}
|
|
128
|
+
|
|
129
|
+
Validation Steps:
|
|
130
|
+
1. Find and validate the correct PR (see PR selection above)
|
|
131
|
+
2. Run tests scoped to the affected packages
|
|
132
|
+
3. Verify the build passes
|
|
133
|
+
4. Check deployment status (CI checks on the PR)
|
|
134
|
+
5. Review changes against issue requirements
|
|
135
|
+
6. Post result comment with the structured marker`;
|
|
49
136
|
break;
|
|
50
137
|
case 'acceptance':
|
|
51
|
-
basePrompt = `Process acceptance for ${identifier}. Validate development and QA work is complete, verify PR is ready to merge (CI passing, no conflicts), merge the PR, and clean up local resources
|
|
138
|
+
basePrompt = `Process acceptance for ${identifier}. Validate development and QA work is complete, verify PR is ready to merge (CI passing, no conflicts), merge the PR, and clean up local resources.
|
|
139
|
+
${WORK_RESULT_MARKER_INSTRUCTION}
|
|
140
|
+
${PR_SELECTION_GUIDANCE}
|
|
141
|
+
|
|
142
|
+
Acceptance Steps:
|
|
143
|
+
1. Find and validate the correct PR (see PR selection above)
|
|
144
|
+
2. Verify CI is passing and there are no merge conflicts
|
|
145
|
+
3. Confirm QA has passed (check issue status or QA comments)
|
|
146
|
+
4. Merge the PR
|
|
147
|
+
5. Delete the remote branch after successful merge
|
|
148
|
+
6. Post result comment with the structured marker`;
|
|
52
149
|
break;
|
|
53
150
|
case 'refinement':
|
|
54
151
|
basePrompt = `Refine ${identifier} based on rejection feedback. Read comments, update requirements, then return to Backlog.`;
|
|
@@ -67,13 +164,19 @@ Before marking the parent issue as complete, verify ALL sub-issues are in Finish
|
|
|
67
164
|
If any sub-issue is not Finished, report the failure and do not mark the parent as complete.`;
|
|
68
165
|
break;
|
|
69
166
|
case 'qa-coordination':
|
|
70
|
-
basePrompt = `Coordinate QA across sub-issues for parent issue ${identifier}. Fetch sub-issues, spawn QA sub-agents in parallel for each sub-issue, collect pass/fail results, and roll up to parent. ALL sub-issues must pass QA for the parent to pass
|
|
167
|
+
basePrompt = `Coordinate QA across sub-issues for parent issue ${identifier}. Fetch sub-issues, spawn QA sub-agents in parallel for each sub-issue, collect pass/fail results, and roll up to parent. ALL sub-issues must pass QA for the parent to pass.
|
|
168
|
+
${WORK_RESULT_MARKER_INSTRUCTION}`;
|
|
71
169
|
break;
|
|
72
170
|
case 'acceptance-coordination':
|
|
73
|
-
basePrompt = `Coordinate acceptance across sub-issues for parent issue ${identifier}. Verify all sub-issues are Delivered, validate the PR (CI passing, no conflicts), merge the PR, and bulk-update sub-issues to Accepted
|
|
171
|
+
basePrompt = `Coordinate acceptance across sub-issues for parent issue ${identifier}. Verify all sub-issues are Delivered, validate the PR (CI passing, no conflicts), merge the PR, and bulk-update sub-issues to Accepted.
|
|
172
|
+
${WORK_RESULT_MARKER_INSTRUCTION}`;
|
|
74
173
|
break;
|
|
75
174
|
}
|
|
76
175
|
basePrompt += HUMAN_BLOCKER_INSTRUCTION;
|
|
176
|
+
// Inject workflow failure context for retries
|
|
177
|
+
if (workflowContext) {
|
|
178
|
+
basePrompt += buildFailureContextBlock(workType, workflowContext);
|
|
179
|
+
}
|
|
77
180
|
if (mentionContext) {
|
|
78
181
|
return `${basePrompt}\n\nAdditional context from the user's mention:\n${mentionContext}`;
|
|
79
182
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.test.d.ts","sourceRoot":"","sources":["../../../src/defaults/prompts.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { buildFailureContextBlock, defaultGeneratePrompt } from './prompts.js';
|
|
3
|
+
describe('buildFailureContextBlock', () => {
|
|
4
|
+
const baseContext = {
|
|
5
|
+
cycleCount: 2,
|
|
6
|
+
strategy: 'context-enriched',
|
|
7
|
+
failureSummary: '--- Cycle 1, qa (2024-01-01) ---\nTests failed: TypeError in UserService',
|
|
8
|
+
};
|
|
9
|
+
describe('refinement work type', () => {
|
|
10
|
+
it('returns empty string for cycleCount 0', () => {
|
|
11
|
+
expect(buildFailureContextBlock('refinement', { ...baseContext, cycleCount: 0 })).toBe('');
|
|
12
|
+
});
|
|
13
|
+
it('includes failure history for cycle 1+', () => {
|
|
14
|
+
const result = buildFailureContextBlock('refinement', baseContext);
|
|
15
|
+
expect(result).toContain('Previous Failure Context');
|
|
16
|
+
expect(result).toContain('2 development-QA cycle(s)');
|
|
17
|
+
expect(result).toContain('TypeError in UserService');
|
|
18
|
+
expect(result).toContain('Do NOT repeat approaches that already failed');
|
|
19
|
+
});
|
|
20
|
+
it('includes decomposition instructions for decompose strategy', () => {
|
|
21
|
+
const decomposeCtx = {
|
|
22
|
+
...baseContext,
|
|
23
|
+
cycleCount: 3,
|
|
24
|
+
strategy: 'decompose',
|
|
25
|
+
};
|
|
26
|
+
const result = buildFailureContextBlock('refinement', decomposeCtx);
|
|
27
|
+
expect(result).toContain('Decomposition Required');
|
|
28
|
+
expect(result).toContain('DECOMPOSE this issue');
|
|
29
|
+
expect(result).toContain('smaller sub-issues');
|
|
30
|
+
expect(result).not.toContain('Previous Failure Context');
|
|
31
|
+
});
|
|
32
|
+
it('handles null failure summary gracefully', () => {
|
|
33
|
+
const result = buildFailureContextBlock('refinement', { ...baseContext, failureSummary: null });
|
|
34
|
+
expect(result).toContain('No details recorded');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
describe('development work type', () => {
|
|
38
|
+
it('returns empty string for cycleCount 0', () => {
|
|
39
|
+
expect(buildFailureContextBlock('development', { ...baseContext, cycleCount: 0 })).toBe('');
|
|
40
|
+
});
|
|
41
|
+
it('includes retry context for cycle 1+', () => {
|
|
42
|
+
const result = buildFailureContextBlock('development', baseContext);
|
|
43
|
+
expect(result).toContain('Retry Context');
|
|
44
|
+
expect(result).toContain('retry #2');
|
|
45
|
+
expect(result).toContain('TypeError in UserService');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
describe('coordination work type', () => {
|
|
49
|
+
it('includes retry context same as development', () => {
|
|
50
|
+
const result = buildFailureContextBlock('coordination', baseContext);
|
|
51
|
+
expect(result).toContain('Retry Context');
|
|
52
|
+
expect(result).toContain('retry #2');
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe('qa work type', () => {
|
|
56
|
+
it('returns empty string when no failure summary', () => {
|
|
57
|
+
expect(buildFailureContextBlock('qa', { ...baseContext, failureSummary: null })).toBe('');
|
|
58
|
+
});
|
|
59
|
+
it('includes previous QA results when failure summary exists', () => {
|
|
60
|
+
const qaCtx = {
|
|
61
|
+
...baseContext,
|
|
62
|
+
qaAttemptCount: 2,
|
|
63
|
+
};
|
|
64
|
+
const result = buildFailureContextBlock('qa', qaCtx);
|
|
65
|
+
expect(result).toContain('Previous QA Results');
|
|
66
|
+
expect(result).toContain('QA\'d 2 times previously');
|
|
67
|
+
expect(result).toContain('TypeError in UserService');
|
|
68
|
+
});
|
|
69
|
+
it('falls back to cycleCount when qaAttemptCount not provided', () => {
|
|
70
|
+
const result = buildFailureContextBlock('qa', baseContext);
|
|
71
|
+
expect(result).toContain('QA\'d 2 times previously');
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
describe('other work types', () => {
|
|
75
|
+
it('returns empty string for acceptance', () => {
|
|
76
|
+
expect(buildFailureContextBlock('acceptance', baseContext)).toBe('');
|
|
77
|
+
});
|
|
78
|
+
it('returns empty string for research', () => {
|
|
79
|
+
expect(buildFailureContextBlock('research', baseContext)).toBe('');
|
|
80
|
+
});
|
|
81
|
+
it('returns empty string for backlog-creation', () => {
|
|
82
|
+
expect(buildFailureContextBlock('backlog-creation', baseContext)).toBe('');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
describe('defaultGeneratePrompt with workflowContext', () => {
|
|
87
|
+
it('does not modify prompt when workflowContext is undefined', () => {
|
|
88
|
+
const withoutCtx = defaultGeneratePrompt('PROJ-123', 'development');
|
|
89
|
+
const withUndefined = defaultGeneratePrompt('PROJ-123', 'development', undefined, undefined);
|
|
90
|
+
expect(withoutCtx).toBe(withUndefined);
|
|
91
|
+
});
|
|
92
|
+
it('does not modify prompt when cycleCount is 0', () => {
|
|
93
|
+
const withoutCtx = defaultGeneratePrompt('PROJ-123', 'development');
|
|
94
|
+
const withZero = defaultGeneratePrompt('PROJ-123', 'development', undefined, {
|
|
95
|
+
cycleCount: 0,
|
|
96
|
+
strategy: 'normal',
|
|
97
|
+
failureSummary: null,
|
|
98
|
+
});
|
|
99
|
+
expect(withoutCtx).toBe(withZero);
|
|
100
|
+
});
|
|
101
|
+
it('enriches refinement prompt with failure context', () => {
|
|
102
|
+
const result = defaultGeneratePrompt('PROJ-123', 'refinement', undefined, {
|
|
103
|
+
cycleCount: 2,
|
|
104
|
+
strategy: 'context-enriched',
|
|
105
|
+
failureSummary: 'Tests failed in UserService',
|
|
106
|
+
});
|
|
107
|
+
expect(result).toContain('Refine PROJ-123');
|
|
108
|
+
expect(result).toContain('Previous Failure Context');
|
|
109
|
+
expect(result).toContain('Tests failed in UserService');
|
|
110
|
+
});
|
|
111
|
+
it('enriches development prompt with retry context', () => {
|
|
112
|
+
const result = defaultGeneratePrompt('PROJ-123', 'development', undefined, {
|
|
113
|
+
cycleCount: 1,
|
|
114
|
+
strategy: 'normal',
|
|
115
|
+
failureSummary: 'Build error in core package',
|
|
116
|
+
});
|
|
117
|
+
expect(result).toContain('Start work on PROJ-123');
|
|
118
|
+
expect(result).toContain('Retry Context');
|
|
119
|
+
expect(result).toContain('Build error in core package');
|
|
120
|
+
});
|
|
121
|
+
it('preserves mentionContext alongside workflowContext', () => {
|
|
122
|
+
const result = defaultGeneratePrompt('PROJ-123', 'development', 'Please focus on the API layer', {
|
|
123
|
+
cycleCount: 1,
|
|
124
|
+
strategy: 'normal',
|
|
125
|
+
failureSummary: 'API tests failing',
|
|
126
|
+
});
|
|
127
|
+
expect(result).toContain('Retry Context');
|
|
128
|
+
expect(result).toContain('Please focus on the API layer');
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LinearFrontendAdapter
|
|
3
|
+
*
|
|
4
|
+
* Implements the WorkSchedulingFrontend interface (defined in @supaku/agentfactory)
|
|
5
|
+
* by wrapping the LinearAgentClient. This adapter translates between abstract,
|
|
6
|
+
* frontend-agnostic types and Linear-specific concepts.
|
|
7
|
+
*
|
|
8
|
+
* Structural typing note: This class structurally satisfies the WorkSchedulingFrontend
|
|
9
|
+
* interface from @supaku/agentfactory without an explicit `implements` clause, avoiding
|
|
10
|
+
* a circular package dependency (core depends on linear at runtime, linear depends on
|
|
11
|
+
* core only for types). Consumers who import both packages can assign this to
|
|
12
|
+
* WorkSchedulingFrontend.
|
|
13
|
+
*/
|
|
14
|
+
import type { LinearAgentClient } from './agent-client.js';
|
|
15
|
+
/**
|
|
16
|
+
* Abstract workflow statuses that map to Linear-specific status names.
|
|
17
|
+
*/
|
|
18
|
+
export type AbstractStatus = 'icebox' | 'backlog' | 'started' | 'finished' | 'delivered' | 'accepted' | 'rejected' | 'canceled';
|
|
19
|
+
/**
|
|
20
|
+
* Minimal issue representation shared across frontends.
|
|
21
|
+
*/
|
|
22
|
+
export interface AbstractIssue {
|
|
23
|
+
id: string;
|
|
24
|
+
identifier: string;
|
|
25
|
+
title: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
url: string;
|
|
28
|
+
status: AbstractStatus;
|
|
29
|
+
priority: number;
|
|
30
|
+
labels: string[];
|
|
31
|
+
parentId?: string;
|
|
32
|
+
project?: string;
|
|
33
|
+
createdAt: Date;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Minimal comment representation.
|
|
37
|
+
*/
|
|
38
|
+
export interface AbstractComment {
|
|
39
|
+
id: string;
|
|
40
|
+
body: string;
|
|
41
|
+
userId?: string;
|
|
42
|
+
userName?: string;
|
|
43
|
+
createdAt: Date;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* External URL for agent sessions.
|
|
47
|
+
*/
|
|
48
|
+
export interface ExternalUrl {
|
|
49
|
+
label: string;
|
|
50
|
+
url: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Input for creating an issue.
|
|
54
|
+
*/
|
|
55
|
+
export interface CreateIssueInput {
|
|
56
|
+
title: string;
|
|
57
|
+
teamId?: string;
|
|
58
|
+
description?: string;
|
|
59
|
+
projectId?: string;
|
|
60
|
+
status?: AbstractStatus;
|
|
61
|
+
labels?: string[];
|
|
62
|
+
parentId?: string;
|
|
63
|
+
priority?: number;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Input for creating a blocker issue.
|
|
67
|
+
*/
|
|
68
|
+
export interface CreateBlockerInput {
|
|
69
|
+
title: string;
|
|
70
|
+
description?: string;
|
|
71
|
+
teamId?: string;
|
|
72
|
+
projectId?: string;
|
|
73
|
+
assignee?: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Input for updating an agent session.
|
|
77
|
+
*/
|
|
78
|
+
export interface UpdateSessionInput {
|
|
79
|
+
externalUrls?: ExternalUrl[];
|
|
80
|
+
plan?: Array<{
|
|
81
|
+
content: string;
|
|
82
|
+
status: 'pending' | 'inProgress' | 'completed' | 'canceled';
|
|
83
|
+
}>;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Linear frontend adapter that wraps LinearAgentClient.
|
|
87
|
+
*
|
|
88
|
+
* Structurally satisfies WorkSchedulingFrontend from @supaku/agentfactory.
|
|
89
|
+
*/
|
|
90
|
+
export declare class LinearFrontendAdapter {
|
|
91
|
+
private readonly client;
|
|
92
|
+
readonly name: "linear";
|
|
93
|
+
constructor(client: LinearAgentClient);
|
|
94
|
+
/**
|
|
95
|
+
* Resolve an abstract status to its Linear-native name.
|
|
96
|
+
*/
|
|
97
|
+
resolveStatus(abstract: AbstractStatus): string;
|
|
98
|
+
/**
|
|
99
|
+
* Map a Linear-native status name to its abstract equivalent.
|
|
100
|
+
* Defaults to 'backlog' for unknown statuses.
|
|
101
|
+
*/
|
|
102
|
+
abstractStatus(nativeStatus: string): AbstractStatus;
|
|
103
|
+
/**
|
|
104
|
+
* Fetch an issue by ID or identifier and map to AbstractIssue.
|
|
105
|
+
*/
|
|
106
|
+
getIssue(id: string): Promise<AbstractIssue>;
|
|
107
|
+
/**
|
|
108
|
+
* List issues in a project filtered by abstract status.
|
|
109
|
+
*
|
|
110
|
+
* Uses the underlying LinearClient directly since LinearAgentClient
|
|
111
|
+
* does not expose a project-scoped issue listing method.
|
|
112
|
+
*/
|
|
113
|
+
listIssuesByStatus(project: string, status: AbstractStatus): Promise<AbstractIssue[]>;
|
|
114
|
+
/**
|
|
115
|
+
* Get comments for an issue and map to AbstractComment[].
|
|
116
|
+
*/
|
|
117
|
+
getIssueComments(id: string): Promise<AbstractComment[]>;
|
|
118
|
+
/**
|
|
119
|
+
* List issues in a project filtered by abstract status, excluding
|
|
120
|
+
* those that are blocked by other issues (have incoming "blocks" relations).
|
|
121
|
+
*/
|
|
122
|
+
getUnblockedIssues(project: string, status: AbstractStatus): Promise<AbstractIssue[]>;
|
|
123
|
+
/**
|
|
124
|
+
* Check if an issue has child issues (is a parent issue).
|
|
125
|
+
*/
|
|
126
|
+
isParentIssue(id: string): Promise<boolean>;
|
|
127
|
+
/**
|
|
128
|
+
* Check if an issue has a parent (is a child/sub-issue).
|
|
129
|
+
*/
|
|
130
|
+
isChildIssue(id: string): Promise<boolean>;
|
|
131
|
+
/**
|
|
132
|
+
* Get sub-issues of a parent issue, mapped to AbstractIssue[].
|
|
133
|
+
*/
|
|
134
|
+
getSubIssues(id: string): Promise<AbstractIssue[]>;
|
|
135
|
+
/**
|
|
136
|
+
* Transition an issue to a new status.
|
|
137
|
+
*/
|
|
138
|
+
transitionIssue(id: string, status: AbstractStatus): Promise<void>;
|
|
139
|
+
/**
|
|
140
|
+
* Create a comment on an issue.
|
|
141
|
+
*/
|
|
142
|
+
createComment(id: string, body: string): Promise<void>;
|
|
143
|
+
/**
|
|
144
|
+
* Create a blocker issue that blocks the source issue.
|
|
145
|
+
* Creates the issue in Icebox with a "Needs Human" label (if found),
|
|
146
|
+
* then creates a "blocks" relation from the blocker to the source.
|
|
147
|
+
*/
|
|
148
|
+
createBlockerIssue(sourceId: string, data: CreateBlockerInput): Promise<AbstractIssue>;
|
|
149
|
+
/**
|
|
150
|
+
* Create a new issue and return it as AbstractIssue.
|
|
151
|
+
*/
|
|
152
|
+
createIssue(data: CreateIssueInput): Promise<AbstractIssue>;
|
|
153
|
+
/**
|
|
154
|
+
* Create an agent session on an issue.
|
|
155
|
+
* Returns the session ID.
|
|
156
|
+
*/
|
|
157
|
+
createAgentSession(issueId: string, externalUrls?: ExternalUrl[]): Promise<string>;
|
|
158
|
+
/**
|
|
159
|
+
* Update an existing agent session.
|
|
160
|
+
*/
|
|
161
|
+
updateAgentSession(sessionId: string, data: UpdateSessionInput): Promise<void>;
|
|
162
|
+
/**
|
|
163
|
+
* Create an activity on an agent session.
|
|
164
|
+
* Wraps the content as a ThoughtActivityContent.
|
|
165
|
+
*/
|
|
166
|
+
createActivity(sessionId: string, type: string, content: string): Promise<void>;
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=frontend-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontend-adapter.d.ts","sourceRoot":"","sources":["../../src/frontend-adapter.ts"],"names":[],"mappings":"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"}
|