sequant 1.20.3 → 2.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/.claude-plugin/marketplace.json +2 -4
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +36 -15
- package/dist/bin/cli.js +25 -2
- package/dist/src/commands/doctor.js +42 -9
- package/dist/src/commands/init.d.ts +1 -0
- package/dist/src/commands/init.js +52 -0
- package/dist/src/commands/logs.d.ts +1 -0
- package/dist/src/commands/logs.js +18 -2
- package/dist/src/commands/run.d.ts +7 -0
- package/dist/src/commands/run.js +235 -68
- package/dist/src/commands/serve.d.ts +13 -0
- package/dist/src/commands/serve.js +131 -0
- package/dist/src/commands/stats.d.ts +1 -0
- package/dist/src/commands/stats.js +185 -26
- package/dist/src/commands/status.d.ts +2 -0
- package/dist/src/commands/status.js +99 -50
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +4 -1
- package/dist/src/lib/ac-parser.d.ts +2 -0
- package/dist/src/lib/ac-parser.js +12 -2
- package/dist/src/lib/assess-comment-parser.d.ts +137 -0
- package/dist/src/lib/assess-comment-parser.js +344 -0
- package/dist/src/lib/ci/config.d.ts +22 -0
- package/dist/src/lib/ci/config.js +134 -0
- package/dist/src/lib/ci/index.d.ts +12 -0
- package/dist/src/lib/ci/index.js +10 -0
- package/dist/src/lib/ci/inputs.d.ts +29 -0
- package/dist/src/lib/ci/inputs.js +103 -0
- package/dist/src/lib/ci/labels.d.ts +34 -0
- package/dist/src/lib/ci/labels.js +101 -0
- package/dist/src/lib/ci/outputs.d.ts +25 -0
- package/dist/src/lib/ci/outputs.js +84 -0
- package/dist/src/lib/ci/triggers.d.ts +9 -0
- package/dist/src/lib/ci/triggers.js +86 -0
- package/dist/src/lib/ci/types.d.ts +131 -0
- package/dist/src/lib/ci/types.js +47 -0
- package/dist/src/lib/mcp-config.d.ts +54 -0
- package/dist/src/lib/mcp-config.js +172 -0
- package/dist/src/lib/merge-check/index.js +6 -12
- package/dist/src/lib/merge-check/types.d.ts +20 -7
- package/dist/src/lib/merge-check/types.js +11 -0
- package/dist/src/lib/phase-signal.d.ts +3 -3
- package/dist/src/lib/phase-signal.js +5 -3
- package/dist/src/lib/settings.d.ts +52 -0
- package/dist/src/lib/settings.js +41 -0
- package/dist/src/lib/shutdown.d.ts +16 -5
- package/dist/src/lib/shutdown.js +32 -12
- package/dist/src/lib/solve-comment-parser.d.ts +9 -102
- package/dist/src/lib/solve-comment-parser.js +13 -248
- package/dist/src/lib/stacks.d.ts +8 -0
- package/dist/src/lib/stacks.js +34 -0
- package/dist/src/lib/system.js +3 -7
- package/dist/src/lib/test-tautology-detector.d.ts +10 -0
- package/dist/src/lib/test-tautology-detector.js +43 -4
- package/dist/src/lib/upstream/assessment.js +9 -59
- package/dist/src/lib/upstream/issues.js +12 -75
- package/dist/src/lib/version-check.d.ts +2 -2
- package/dist/src/lib/version-check.js +6 -3
- package/dist/src/lib/version.d.ts +4 -0
- package/dist/src/lib/version.js +25 -0
- package/dist/src/lib/workflow/batch-executor.d.ts +26 -86
- package/dist/src/lib/workflow/batch-executor.js +269 -55
- package/dist/src/lib/workflow/drivers/agent-driver.d.ts +56 -0
- package/dist/src/lib/workflow/drivers/agent-driver.js +8 -0
- package/dist/src/lib/workflow/drivers/aider.d.ts +18 -0
- package/dist/src/lib/workflow/drivers/aider.js +160 -0
- package/dist/src/lib/workflow/drivers/claude-code.d.ts +17 -0
- package/dist/src/lib/workflow/drivers/claude-code.js +165 -0
- package/dist/src/lib/workflow/drivers/index.d.ts +20 -0
- package/dist/src/lib/workflow/drivers/index.js +27 -0
- package/dist/src/lib/workflow/error-classifier.d.ts +16 -0
- package/dist/src/lib/workflow/error-classifier.js +90 -0
- package/dist/src/lib/workflow/log-writer.d.ts +6 -3
- package/dist/src/lib/workflow/log-writer.js +57 -27
- package/dist/src/lib/workflow/metrics-schema.d.ts +9 -9
- package/dist/src/lib/workflow/phase-detection.d.ts +23 -0
- package/dist/src/lib/workflow/phase-detection.js +45 -29
- package/dist/src/lib/workflow/phase-executor.d.ts +42 -3
- package/dist/src/lib/workflow/phase-executor.js +375 -229
- package/dist/src/lib/workflow/phase-mapper.d.ts +1 -1
- package/dist/src/lib/workflow/phase-mapper.js +7 -7
- package/dist/src/lib/workflow/platforms/github.d.ts +157 -0
- package/dist/src/lib/workflow/platforms/github.js +466 -0
- package/dist/src/lib/workflow/platforms/index.d.ts +17 -0
- package/dist/src/lib/workflow/platforms/index.js +25 -0
- package/dist/src/lib/workflow/platforms/platform-provider.d.ts +67 -0
- package/dist/src/lib/workflow/platforms/platform-provider.js +8 -0
- package/dist/src/lib/workflow/pr-status.d.ts +2 -4
- package/dist/src/lib/workflow/pr-status.js +3 -16
- package/dist/src/lib/workflow/qa-cache.d.ts +58 -0
- package/dist/src/lib/workflow/qa-cache.js +88 -0
- package/dist/src/lib/workflow/reconcile.d.ts +69 -0
- package/dist/src/lib/workflow/reconcile.js +290 -0
- package/dist/src/lib/workflow/ring-buffer.d.ts +17 -0
- package/dist/src/lib/workflow/ring-buffer.js +37 -0
- package/dist/src/lib/workflow/run-log-schema.d.ts +115 -24
- package/dist/src/lib/workflow/run-log-schema.js +47 -12
- package/dist/src/lib/workflow/run-reflect.js +1 -1
- package/dist/src/lib/workflow/state-cleanup.js +21 -0
- package/dist/src/lib/workflow/state-manager.d.ts +34 -3
- package/dist/src/lib/workflow/state-manager.js +278 -126
- package/dist/src/lib/workflow/state-schema.d.ts +34 -30
- package/dist/src/lib/workflow/state-schema.js +35 -25
- package/dist/src/lib/workflow/state-utils.d.ts +3 -1
- package/dist/src/lib/workflow/state-utils.js +1 -0
- package/dist/src/lib/workflow/types.d.ts +224 -6
- package/dist/src/lib/workflow/types.js +20 -1
- package/dist/src/lib/workflow/worktree-discovery.d.ts +1 -1
- package/dist/src/lib/workflow/worktree-discovery.js +6 -14
- package/dist/src/lib/workflow/worktree-manager.js +33 -51
- package/dist/src/mcp/index.d.ts +4 -0
- package/dist/src/mcp/index.js +4 -0
- package/dist/src/mcp/resources.d.ts +7 -0
- package/dist/src/mcp/resources.js +111 -0
- package/dist/src/mcp/run-registry.d.ts +34 -0
- package/dist/src/mcp/run-registry.js +42 -0
- package/dist/src/mcp/server.d.ts +12 -0
- package/dist/src/mcp/server.js +50 -0
- package/dist/src/mcp/tools/logs.d.ts +7 -0
- package/dist/src/mcp/tools/logs.js +149 -0
- package/dist/src/mcp/tools/run.d.ts +121 -0
- package/dist/src/mcp/tools/run.js +591 -0
- package/dist/src/mcp/tools/status.d.ts +7 -0
- package/dist/src/mcp/tools/status.js +127 -0
- package/package.json +26 -7
- package/templates/hooks/post-tool.sh +19 -8
- package/templates/hooks/pre-tool.sh +36 -49
- package/templates/mcp.json +6 -0
- package/templates/skills/assess/SKILL.md +354 -352
- package/templates/skills/exec/SKILL.md +64 -1
- package/templates/skills/fullsolve/SKILL.md +35 -4
- package/templates/skills/qa/SKILL.md +486 -9
- package/templates/skills/qa/scripts/quality-checks.sh +1 -1
- package/templates/skills/setup/SKILL.md +386 -0
- package/templates/skills/solve/SKILL.md +38 -664
- package/templates/skills/spec/SKILL.md +90 -31
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assess Comment Parser
|
|
3
|
+
*
|
|
4
|
+
* Detects and parses /assess command output from GitHub issue comments.
|
|
5
|
+
* This is the unified parser that handles both the new /assess format
|
|
6
|
+
* and the legacy /solve format for backward compatibility.
|
|
7
|
+
*
|
|
8
|
+
* When an assess comment exists, its phase recommendations take precedence
|
|
9
|
+
* over content analysis (but not over labels).
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { findAssessComment, parseAssessWorkflow } from './assess-comment-parser';
|
|
14
|
+
*
|
|
15
|
+
* const comments = [
|
|
16
|
+
* { body: "Some regular comment" },
|
|
17
|
+
* { body: "## Assess Analysis\n\n→ PROCEED — Ready.\n\n<!-- assess:phases=spec,exec,qa -->" },
|
|
18
|
+
* ];
|
|
19
|
+
*
|
|
20
|
+
* const assessComment = findAssessComment(comments);
|
|
21
|
+
* if (assessComment) {
|
|
22
|
+
* const workflow = parseAssessWorkflow(assessComment.body);
|
|
23
|
+
* // workflow: { phases: ['spec', 'exec', 'qa'], action: 'PROCEED', ... }
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
import type { Phase } from "./workflow/types.js";
|
|
28
|
+
import type { PhaseSignal } from "./phase-signal.js";
|
|
29
|
+
/**
|
|
30
|
+
* The 6 fixed actions that /assess can recommend
|
|
31
|
+
*/
|
|
32
|
+
export type AssessAction = "PROCEED" | "CLOSE" | "MERGE" | "REWRITE" | "CLARIFY" | "PARK";
|
|
33
|
+
/**
|
|
34
|
+
* Result of parsing an assess comment (superset of SolveWorkflowResult)
|
|
35
|
+
*/
|
|
36
|
+
export interface AssessWorkflowResult {
|
|
37
|
+
/** Phases recommended by assess (only meaningful for PROCEED) */
|
|
38
|
+
phases: Phase[];
|
|
39
|
+
/** Whether quality loop is recommended */
|
|
40
|
+
qualityLoop: boolean;
|
|
41
|
+
/** The issue numbers mentioned in the assess comment */
|
|
42
|
+
issueNumbers: number[];
|
|
43
|
+
/** Raw workflow string (e.g., "spec → exec → qa") */
|
|
44
|
+
workflowString?: string;
|
|
45
|
+
/** The recommended action (one of the 6 fixed actions) */
|
|
46
|
+
action?: AssessAction;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Backward-compatible alias for AssessWorkflowResult
|
|
50
|
+
*/
|
|
51
|
+
export type SolveWorkflowResult = AssessWorkflowResult;
|
|
52
|
+
/**
|
|
53
|
+
* Comment structure (simplified from GitHub API)
|
|
54
|
+
*/
|
|
55
|
+
export interface IssueComment {
|
|
56
|
+
body: string;
|
|
57
|
+
author?: {
|
|
58
|
+
login: string;
|
|
59
|
+
};
|
|
60
|
+
createdAt?: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Structured data extracted from HTML comment markers
|
|
64
|
+
*/
|
|
65
|
+
export interface AssessMarkers {
|
|
66
|
+
/** Recommended phases */
|
|
67
|
+
phases?: string[];
|
|
68
|
+
/** Whether quality loop is recommended */
|
|
69
|
+
qualityLoop?: boolean;
|
|
70
|
+
/** The recommended action */
|
|
71
|
+
action?: AssessAction;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Backward-compatible alias for AssessMarkers
|
|
75
|
+
*/
|
|
76
|
+
export type SolveMarkers = AssessMarkers;
|
|
77
|
+
/**
|
|
78
|
+
* Check if a comment is an assess command output (new format)
|
|
79
|
+
*/
|
|
80
|
+
export declare function isAssessComment(body: string): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Check if a comment is a solve command output (legacy format)
|
|
83
|
+
* @deprecated Use isAssessComment instead
|
|
84
|
+
*/
|
|
85
|
+
export declare function isSolveComment(body: string): boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Find the most recent assess/solve comment from a list of comments
|
|
88
|
+
*/
|
|
89
|
+
export declare function findAssessComment(comments: IssueComment[]): IssueComment | null;
|
|
90
|
+
/**
|
|
91
|
+
* Find the most recent solve comment from a list of comments
|
|
92
|
+
* @deprecated Use findAssessComment instead
|
|
93
|
+
*/
|
|
94
|
+
export declare function findSolveComment(comments: IssueComment[]): IssueComment | null;
|
|
95
|
+
/**
|
|
96
|
+
* Parse HTML comment markers from a comment body
|
|
97
|
+
*
|
|
98
|
+
* Supports both `assess:` and `solve:` prefixes.
|
|
99
|
+
* When both are present, `assess:` markers take precedence.
|
|
100
|
+
*/
|
|
101
|
+
export declare function parseAssessMarkers(body: string): AssessMarkers;
|
|
102
|
+
/**
|
|
103
|
+
* Parse HTML comment markers (backward-compatible alias)
|
|
104
|
+
* @deprecated Use parseAssessMarkers instead
|
|
105
|
+
*/
|
|
106
|
+
export declare function parseSolveMarkers(body: string): AssessMarkers;
|
|
107
|
+
/**
|
|
108
|
+
* Parse an assess/solve comment to extract workflow information
|
|
109
|
+
*/
|
|
110
|
+
export declare function parseAssessWorkflow(body: string): AssessWorkflowResult;
|
|
111
|
+
/**
|
|
112
|
+
* Parse a solve comment to extract workflow information
|
|
113
|
+
* @deprecated Use parseAssessWorkflow instead
|
|
114
|
+
*/
|
|
115
|
+
export declare function parseSolveWorkflow(body: string): AssessWorkflowResult;
|
|
116
|
+
/**
|
|
117
|
+
* Convert assess workflow result to phase signals
|
|
118
|
+
*/
|
|
119
|
+
export declare function assessWorkflowToSignals(workflow: AssessWorkflowResult): PhaseSignal[];
|
|
120
|
+
/**
|
|
121
|
+
* Convert solve workflow result to phase signals
|
|
122
|
+
*
|
|
123
|
+
* Preserves "/solve" wording in reason strings for backward compatibility
|
|
124
|
+
* with existing tests and consumers.
|
|
125
|
+
*
|
|
126
|
+
* @deprecated Use assessWorkflowToSignals instead
|
|
127
|
+
*/
|
|
128
|
+
export declare function solveWorkflowToSignals(workflow: AssessWorkflowResult): PhaseSignal[];
|
|
129
|
+
/**
|
|
130
|
+
* Check if assess/solve comment covers the current issue
|
|
131
|
+
*/
|
|
132
|
+
export declare function assessCoversIssue(workflow: AssessWorkflowResult, issueNumber: number): boolean;
|
|
133
|
+
/**
|
|
134
|
+
* Check if solve comment covers the current issue
|
|
135
|
+
* @deprecated Use assessCoversIssue instead
|
|
136
|
+
*/
|
|
137
|
+
export declare function solveCoversIssue(workflow: AssessWorkflowResult, issueNumber: number): boolean;
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assess Comment Parser
|
|
3
|
+
*
|
|
4
|
+
* Detects and parses /assess command output from GitHub issue comments.
|
|
5
|
+
* This is the unified parser that handles both the new /assess format
|
|
6
|
+
* and the legacy /solve format for backward compatibility.
|
|
7
|
+
*
|
|
8
|
+
* When an assess comment exists, its phase recommendations take precedence
|
|
9
|
+
* over content analysis (but not over labels).
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { findAssessComment, parseAssessWorkflow } from './assess-comment-parser';
|
|
14
|
+
*
|
|
15
|
+
* const comments = [
|
|
16
|
+
* { body: "Some regular comment" },
|
|
17
|
+
* { body: "## Assess Analysis\n\n→ PROCEED — Ready.\n\n<!-- assess:phases=spec,exec,qa -->" },
|
|
18
|
+
* ];
|
|
19
|
+
*
|
|
20
|
+
* const assessComment = findAssessComment(comments);
|
|
21
|
+
* if (assessComment) {
|
|
22
|
+
* const workflow = parseAssessWorkflow(assessComment.body);
|
|
23
|
+
* // workflow: { phases: ['spec', 'exec', 'qa'], action: 'PROCEED', ... }
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Markers that indicate an assess comment (new format)
|
|
29
|
+
*/
|
|
30
|
+
const ASSESS_MARKERS = [
|
|
31
|
+
"## Assess Analysis",
|
|
32
|
+
"## Assess Workflow",
|
|
33
|
+
"*Generated by `/assess",
|
|
34
|
+
];
|
|
35
|
+
/**
|
|
36
|
+
* Markers that indicate a solve comment (legacy format)
|
|
37
|
+
*/
|
|
38
|
+
const SOLVE_MARKERS = [
|
|
39
|
+
"## Solve Analysis",
|
|
40
|
+
"## Solve Workflow for Issues:",
|
|
41
|
+
"## Solve Workflow for Issue:",
|
|
42
|
+
"### Recommended Workflow",
|
|
43
|
+
"*📝 Generated by `/solve",
|
|
44
|
+
];
|
|
45
|
+
/**
|
|
46
|
+
* All markers (both assess and solve)
|
|
47
|
+
*/
|
|
48
|
+
const ALL_MARKERS = [...ASSESS_MARKERS, ...SOLVE_MARKERS];
|
|
49
|
+
/**
|
|
50
|
+
* Pattern to extract phases from workflow string
|
|
51
|
+
* Matches: `/spec`, `/exec`, `/test`, `/qa`, etc.
|
|
52
|
+
* Also matches without slash: `spec`, `exec`, `test`, `qa` (for arrow notation)
|
|
53
|
+
*/
|
|
54
|
+
const PHASE_PATTERN = /\/?(?<!\w)(spec|exec|test|qa|security-review|testgen|loop)(?!\w)/g;
|
|
55
|
+
/**
|
|
56
|
+
* Pattern to detect quality loop recommendation
|
|
57
|
+
*/
|
|
58
|
+
const QUALITY_LOOP_PATTERNS = [
|
|
59
|
+
/quality\s*loop.*auto-enable/i,
|
|
60
|
+
/--quality-loop/i,
|
|
61
|
+
/quality\s*loop.*recommended/i,
|
|
62
|
+
/enable.*quality\s*loop/i,
|
|
63
|
+
];
|
|
64
|
+
/**
|
|
65
|
+
* Pattern to extract structured data from HTML comment markers
|
|
66
|
+
* Matches both: <!-- assess:phases=exec,qa --> and <!-- solve:phases=exec,qa -->
|
|
67
|
+
*/
|
|
68
|
+
const HTML_MARKER_PATTERN = /<!--\s*(?:assess|solve):(\w[\w-]*)=([\w,.-]*)\s*-->/g;
|
|
69
|
+
/**
|
|
70
|
+
* Pattern to extract assess-specific HTML markers (takes precedence)
|
|
71
|
+
*/
|
|
72
|
+
const ASSESS_HTML_MARKER_PATTERN = /<!--\s*assess:(\w[\w-]*)=([\w,.-]*)\s*-->/g;
|
|
73
|
+
/**
|
|
74
|
+
* Pattern to extract issue numbers from header
|
|
75
|
+
* Matches: "## Solve Workflow for Issues: 123, 456" or "#123"
|
|
76
|
+
*/
|
|
77
|
+
const ISSUE_NUMBER_PATTERN = /#?(\d+)/g;
|
|
78
|
+
/**
|
|
79
|
+
* Valid assess actions
|
|
80
|
+
*/
|
|
81
|
+
const VALID_ACTIONS = [
|
|
82
|
+
"PROCEED",
|
|
83
|
+
"CLOSE",
|
|
84
|
+
"MERGE",
|
|
85
|
+
"REWRITE",
|
|
86
|
+
"CLARIFY",
|
|
87
|
+
"PARK",
|
|
88
|
+
];
|
|
89
|
+
// ─── Detection Functions ────────────────────────────────────────────────────
|
|
90
|
+
/**
|
|
91
|
+
* Check if a comment is an assess command output (new format)
|
|
92
|
+
*/
|
|
93
|
+
export function isAssessComment(body) {
|
|
94
|
+
return ALL_MARKERS.some((marker) => body.includes(marker));
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Check if a comment is a solve command output (legacy format)
|
|
98
|
+
* @deprecated Use isAssessComment instead
|
|
99
|
+
*/
|
|
100
|
+
export function isSolveComment(body) {
|
|
101
|
+
return isAssessComment(body);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Find the most recent assess/solve comment from a list of comments
|
|
105
|
+
*/
|
|
106
|
+
export function findAssessComment(comments) {
|
|
107
|
+
for (let i = comments.length - 1; i >= 0; i--) {
|
|
108
|
+
if (isAssessComment(comments[i].body)) {
|
|
109
|
+
return comments[i];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Find the most recent solve comment from a list of comments
|
|
116
|
+
* @deprecated Use findAssessComment instead
|
|
117
|
+
*/
|
|
118
|
+
export function findSolveComment(comments) {
|
|
119
|
+
return findAssessComment(comments);
|
|
120
|
+
}
|
|
121
|
+
// ─── Parsing Functions ──────────────────────────────────────────────────────
|
|
122
|
+
/**
|
|
123
|
+
* Parse phases from a workflow string
|
|
124
|
+
*/
|
|
125
|
+
function parseWorkflowString(workflowString) {
|
|
126
|
+
const phases = [];
|
|
127
|
+
const matches = workflowString.matchAll(PHASE_PATTERN);
|
|
128
|
+
for (const match of matches) {
|
|
129
|
+
const phase = match[1];
|
|
130
|
+
if (!phases.includes(phase)) {
|
|
131
|
+
phases.push(phase);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return phases;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Parse HTML comment markers from a comment body
|
|
138
|
+
*
|
|
139
|
+
* Supports both `assess:` and `solve:` prefixes.
|
|
140
|
+
* When both are present, `assess:` markers take precedence.
|
|
141
|
+
*/
|
|
142
|
+
export function parseAssessMarkers(body) {
|
|
143
|
+
const markers = {};
|
|
144
|
+
// First pass: collect all markers (both assess: and solve:)
|
|
145
|
+
const allMatches = body.matchAll(HTML_MARKER_PATTERN);
|
|
146
|
+
for (const match of allMatches) {
|
|
147
|
+
applyMarker(markers, match[1], match[2]);
|
|
148
|
+
}
|
|
149
|
+
// Second pass: assess: markers override solve: markers
|
|
150
|
+
const assessMatches = body.matchAll(ASSESS_HTML_MARKER_PATTERN);
|
|
151
|
+
for (const match of assessMatches) {
|
|
152
|
+
applyMarker(markers, match[1], match[2]);
|
|
153
|
+
}
|
|
154
|
+
return markers;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Apply a single marker key-value pair to the markers object
|
|
158
|
+
*/
|
|
159
|
+
function applyMarker(markers, key, value) {
|
|
160
|
+
switch (key) {
|
|
161
|
+
case "phases":
|
|
162
|
+
markers.phases = value.split(",").filter(Boolean);
|
|
163
|
+
break;
|
|
164
|
+
case "quality-loop":
|
|
165
|
+
markers.qualityLoop = value === "true";
|
|
166
|
+
break;
|
|
167
|
+
case "action":
|
|
168
|
+
if (VALID_ACTIONS.includes(value)) {
|
|
169
|
+
markers.action = value;
|
|
170
|
+
}
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Parse HTML comment markers (backward-compatible alias)
|
|
176
|
+
* @deprecated Use parseAssessMarkers instead
|
|
177
|
+
*/
|
|
178
|
+
export function parseSolveMarkers(body) {
|
|
179
|
+
return parseAssessMarkers(body);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Parse an assess/solve comment to extract workflow information
|
|
183
|
+
*/
|
|
184
|
+
export function parseAssessWorkflow(body) {
|
|
185
|
+
const result = {
|
|
186
|
+
phases: [],
|
|
187
|
+
qualityLoop: false,
|
|
188
|
+
issueNumbers: [],
|
|
189
|
+
};
|
|
190
|
+
// Extract action from "→ ACTION — reason" line
|
|
191
|
+
const actionMatch = body.match(/→\s*(PROCEED|CLOSE|MERGE|REWRITE|CLARIFY|PARK)\s*[—-]/);
|
|
192
|
+
if (actionMatch) {
|
|
193
|
+
result.action = actionMatch[1];
|
|
194
|
+
}
|
|
195
|
+
// Extract issue numbers from header (both old and new formats)
|
|
196
|
+
const headerMatch = body.match(/## (?:Assess|Solve) (?:Workflow for Issues?|Analysis)(?::\s*|\s+for\s+)([^\n]+)/i);
|
|
197
|
+
if (headerMatch) {
|
|
198
|
+
const numberMatches = headerMatch[1].matchAll(ISSUE_NUMBER_PATTERN);
|
|
199
|
+
for (const match of numberMatches) {
|
|
200
|
+
const num = parseInt(match[1], 10);
|
|
201
|
+
if (!isNaN(num) && !result.issueNumbers.includes(num)) {
|
|
202
|
+
result.issueNumbers.push(num);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// Try HTML comment markers first (most reliable, machine-readable)
|
|
207
|
+
const markers = parseAssessMarkers(body);
|
|
208
|
+
// Action from markers overrides inline detection
|
|
209
|
+
if (markers.action) {
|
|
210
|
+
result.action = markers.action;
|
|
211
|
+
}
|
|
212
|
+
if (markers.phases && markers.phases.length > 0) {
|
|
213
|
+
result.phases = markers.phases;
|
|
214
|
+
result.workflowString = markers.phases.join(" → ");
|
|
215
|
+
}
|
|
216
|
+
if (markers.qualityLoop !== undefined) {
|
|
217
|
+
result.qualityLoop = markers.qualityLoop;
|
|
218
|
+
}
|
|
219
|
+
// If we got phases from markers, we're done with phase detection
|
|
220
|
+
if (result.phases.length > 0) {
|
|
221
|
+
return result;
|
|
222
|
+
}
|
|
223
|
+
// Find workflow lines (e.g., "/spec 152" or "spec → exec → qa")
|
|
224
|
+
const lines = body.split("\n");
|
|
225
|
+
// First pass: look for arrow notation (most reliable)
|
|
226
|
+
for (const line of lines) {
|
|
227
|
+
if (line.includes("→") || line.includes("->")) {
|
|
228
|
+
const phases = parseWorkflowString(line);
|
|
229
|
+
if (phases.length > 0) {
|
|
230
|
+
result.phases = phases;
|
|
231
|
+
result.workflowString = line.trim();
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// Second pass: if no arrow notation found, collect phases from multiple lines
|
|
237
|
+
if (result.phases.length === 0) {
|
|
238
|
+
const collectedPhases = [];
|
|
239
|
+
for (const line of lines) {
|
|
240
|
+
if (line.includes("/spec") ||
|
|
241
|
+
line.includes("/exec") ||
|
|
242
|
+
line.includes("/test") ||
|
|
243
|
+
line.includes("/qa")) {
|
|
244
|
+
const linePhases = parseWorkflowString(line);
|
|
245
|
+
for (const phase of linePhases) {
|
|
246
|
+
if (!collectedPhases.includes(phase)) {
|
|
247
|
+
collectedPhases.push(phase);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (collectedPhases.length > 0) {
|
|
253
|
+
result.phases = collectedPhases;
|
|
254
|
+
result.workflowString = collectedPhases.map((p) => `/${p}`).join(" → ");
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
// If no phases found from lines, try full body
|
|
258
|
+
if (result.phases.length === 0) {
|
|
259
|
+
result.phases = parseWorkflowString(body);
|
|
260
|
+
}
|
|
261
|
+
// Check for quality loop recommendation (if not already set by markers)
|
|
262
|
+
if (!result.qualityLoop) {
|
|
263
|
+
for (const pattern of QUALITY_LOOP_PATTERNS) {
|
|
264
|
+
if (pattern.test(body)) {
|
|
265
|
+
result.qualityLoop = true;
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return result;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Parse a solve comment to extract workflow information
|
|
274
|
+
* @deprecated Use parseAssessWorkflow instead
|
|
275
|
+
*/
|
|
276
|
+
export function parseSolveWorkflow(body) {
|
|
277
|
+
return parseAssessWorkflow(body);
|
|
278
|
+
}
|
|
279
|
+
// ─── Signal Conversion ──────────────────────────────────────────────────────
|
|
280
|
+
/**
|
|
281
|
+
* Convert assess workflow result to phase signals
|
|
282
|
+
*/
|
|
283
|
+
export function assessWorkflowToSignals(workflow) {
|
|
284
|
+
const signals = [];
|
|
285
|
+
for (const phase of workflow.phases) {
|
|
286
|
+
signals.push({
|
|
287
|
+
phase,
|
|
288
|
+
source: "assess",
|
|
289
|
+
confidence: "high",
|
|
290
|
+
reason: `Recommended by /assess command: ${workflow.workflowString || phase}`,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
if (workflow.qualityLoop) {
|
|
294
|
+
signals.push({
|
|
295
|
+
phase: "quality-loop",
|
|
296
|
+
source: "assess",
|
|
297
|
+
confidence: "high",
|
|
298
|
+
reason: "Quality loop recommended by /assess command",
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
return signals;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Convert solve workflow result to phase signals
|
|
305
|
+
*
|
|
306
|
+
* Preserves "/solve" wording in reason strings for backward compatibility
|
|
307
|
+
* with existing tests and consumers.
|
|
308
|
+
*
|
|
309
|
+
* @deprecated Use assessWorkflowToSignals instead
|
|
310
|
+
*/
|
|
311
|
+
export function solveWorkflowToSignals(workflow) {
|
|
312
|
+
const signals = [];
|
|
313
|
+
for (const phase of workflow.phases) {
|
|
314
|
+
signals.push({
|
|
315
|
+
phase,
|
|
316
|
+
source: "solve",
|
|
317
|
+
confidence: "high",
|
|
318
|
+
reason: `Recommended by /solve command: ${workflow.workflowString || phase}`,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
if (workflow.qualityLoop) {
|
|
322
|
+
signals.push({
|
|
323
|
+
phase: "quality-loop",
|
|
324
|
+
source: "solve",
|
|
325
|
+
confidence: "high",
|
|
326
|
+
reason: "Quality loop recommended by /solve command",
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
return signals;
|
|
330
|
+
}
|
|
331
|
+
// ─── Utility Functions ──────────────────────────────────────────────────────
|
|
332
|
+
/**
|
|
333
|
+
* Check if assess/solve comment covers the current issue
|
|
334
|
+
*/
|
|
335
|
+
export function assessCoversIssue(workflow, issueNumber) {
|
|
336
|
+
return workflow.issueNumbers.includes(issueNumber);
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Check if solve comment covers the current issue
|
|
340
|
+
* @deprecated Use assessCoversIssue instead
|
|
341
|
+
*/
|
|
342
|
+
export function solveCoversIssue(workflow, issueNumber) {
|
|
343
|
+
return assessCoversIssue(workflow, issueNumber);
|
|
344
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repository-level CI configuration loader.
|
|
3
|
+
*
|
|
4
|
+
* Reads .github/sequant.yml (or .sequant/ci.json) for repo-level defaults
|
|
5
|
+
* that are merged with workflow inputs. This lets teams set default agents,
|
|
6
|
+
* phases, and cost controls without editing workflow files.
|
|
7
|
+
*/
|
|
8
|
+
import { type CIConfig } from "./types.js";
|
|
9
|
+
/**
|
|
10
|
+
* Load CI configuration from the repository.
|
|
11
|
+
*
|
|
12
|
+
* Checks (in order):
|
|
13
|
+
* 1. .github/sequant.yml (YAML — parsed as simple key: value)
|
|
14
|
+
* 2. .sequant/ci.json (JSON)
|
|
15
|
+
*
|
|
16
|
+
* Returns CI_DEFAULTS if no config file is found.
|
|
17
|
+
*/
|
|
18
|
+
export declare function loadCIConfig(repoRoot: string): CIConfig;
|
|
19
|
+
/**
|
|
20
|
+
* Merge config with defaults (for display/documentation purposes).
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveConfig(config: CIConfig): Required<CIConfig>;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repository-level CI configuration loader.
|
|
3
|
+
*
|
|
4
|
+
* Reads .github/sequant.yml (or .sequant/ci.json) for repo-level defaults
|
|
5
|
+
* that are merged with workflow inputs. This lets teams set default agents,
|
|
6
|
+
* phases, and cost controls without editing workflow files.
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { CI_DEFAULTS } from "./types.js";
|
|
11
|
+
const VALID_PHASES = new Set([
|
|
12
|
+
"spec",
|
|
13
|
+
"security-review",
|
|
14
|
+
"testgen",
|
|
15
|
+
"exec",
|
|
16
|
+
"test",
|
|
17
|
+
"qa",
|
|
18
|
+
"loop",
|
|
19
|
+
]);
|
|
20
|
+
const VALID_AGENTS = new Set(["claude-code", "aider", "codex"]);
|
|
21
|
+
/**
|
|
22
|
+
* Load CI configuration from the repository.
|
|
23
|
+
*
|
|
24
|
+
* Checks (in order):
|
|
25
|
+
* 1. .github/sequant.yml (YAML — parsed as simple key: value)
|
|
26
|
+
* 2. .sequant/ci.json (JSON)
|
|
27
|
+
*
|
|
28
|
+
* Returns CI_DEFAULTS if no config file is found.
|
|
29
|
+
*/
|
|
30
|
+
export function loadCIConfig(repoRoot) {
|
|
31
|
+
// Try .github/sequant.yml first
|
|
32
|
+
const yamlPath = join(repoRoot, ".github", "sequant.yml");
|
|
33
|
+
const yamlConfig = tryLoadYaml(yamlPath);
|
|
34
|
+
if (yamlConfig)
|
|
35
|
+
return yamlConfig;
|
|
36
|
+
// Fall back to .sequant/ci.json
|
|
37
|
+
const jsonPath = join(repoRoot, ".sequant", "ci.json");
|
|
38
|
+
const jsonConfig = tryLoadJson(jsonPath);
|
|
39
|
+
if (jsonConfig)
|
|
40
|
+
return jsonConfig;
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Parse a simple YAML config file (key: value, no nested structures).
|
|
45
|
+
* Avoids adding a YAML parser dependency for a simple flat config.
|
|
46
|
+
*/
|
|
47
|
+
function tryLoadYaml(path) {
|
|
48
|
+
let content;
|
|
49
|
+
try {
|
|
50
|
+
content = readFileSync(path, "utf-8");
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const parsed = {};
|
|
56
|
+
for (const line of content.split("\n")) {
|
|
57
|
+
const trimmed = line.trim();
|
|
58
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
59
|
+
continue;
|
|
60
|
+
const colonIdx = trimmed.indexOf(":");
|
|
61
|
+
if (colonIdx === -1)
|
|
62
|
+
continue;
|
|
63
|
+
const key = trimmed.slice(0, colonIdx).trim();
|
|
64
|
+
const value = trimmed.slice(colonIdx + 1).trim();
|
|
65
|
+
parsed[key] = value;
|
|
66
|
+
}
|
|
67
|
+
return normalizeConfig(parsed);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Load and parse a JSON config file.
|
|
71
|
+
*/
|
|
72
|
+
function tryLoadJson(path) {
|
|
73
|
+
try {
|
|
74
|
+
const content = readFileSync(path, "utf-8");
|
|
75
|
+
const parsed = JSON.parse(content);
|
|
76
|
+
return normalizeConfig(parsed);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Normalize a raw config object into validated CIConfig.
|
|
84
|
+
*/
|
|
85
|
+
function normalizeConfig(raw) {
|
|
86
|
+
const config = {};
|
|
87
|
+
if (typeof raw.agent === "string" && VALID_AGENTS.has(raw.agent)) {
|
|
88
|
+
config.agent = raw.agent;
|
|
89
|
+
}
|
|
90
|
+
if (typeof raw.phases === "string") {
|
|
91
|
+
const phases = raw.phases
|
|
92
|
+
.split(",")
|
|
93
|
+
.map((p) => p.trim())
|
|
94
|
+
.filter((p) => VALID_PHASES.has(p));
|
|
95
|
+
if (phases.length > 0)
|
|
96
|
+
config.phases = phases;
|
|
97
|
+
}
|
|
98
|
+
else if (Array.isArray(raw.phases)) {
|
|
99
|
+
const phases = raw.phases
|
|
100
|
+
.filter((p) => typeof p === "string")
|
|
101
|
+
.filter((p) => VALID_PHASES.has(p));
|
|
102
|
+
if (phases.length > 0)
|
|
103
|
+
config.phases = phases;
|
|
104
|
+
}
|
|
105
|
+
if (typeof raw.timeout === "number" && raw.timeout >= 60) {
|
|
106
|
+
config.timeout = raw.timeout;
|
|
107
|
+
}
|
|
108
|
+
else if (typeof raw.timeout === "string") {
|
|
109
|
+
const n = parseInt(raw.timeout, 10);
|
|
110
|
+
if (!isNaN(n) && n >= 60)
|
|
111
|
+
config.timeout = n;
|
|
112
|
+
}
|
|
113
|
+
if (typeof raw.qualityLoop === "boolean") {
|
|
114
|
+
config.qualityLoop = raw.qualityLoop;
|
|
115
|
+
}
|
|
116
|
+
else if (typeof raw.qualityLoop === "string") {
|
|
117
|
+
config.qualityLoop = raw.qualityLoop === "true";
|
|
118
|
+
}
|
|
119
|
+
const maxRuns = typeof raw.maxConcurrentRuns === "number"
|
|
120
|
+
? raw.maxConcurrentRuns
|
|
121
|
+
: typeof raw.maxConcurrentRuns === "string"
|
|
122
|
+
? parseInt(raw.maxConcurrentRuns, 10)
|
|
123
|
+
: NaN;
|
|
124
|
+
if (!isNaN(maxRuns) && maxRuns >= 1) {
|
|
125
|
+
config.maxConcurrentRuns = maxRuns;
|
|
126
|
+
}
|
|
127
|
+
return config;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Merge config with defaults (for display/documentation purposes).
|
|
131
|
+
*/
|
|
132
|
+
export function resolveConfig(config) {
|
|
133
|
+
return { ...CI_DEFAULTS, ...config };
|
|
134
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CI integration module — provides everything needed to run sequant
|
|
3
|
+
* workflows from GitHub Actions or other CI/CD systems.
|
|
4
|
+
*/
|
|
5
|
+
export { loadCIConfig, resolveConfig } from "./config.js";
|
|
6
|
+
export { parseInputs, validateInputs } from "./inputs.js";
|
|
7
|
+
export type { RawActionInputs } from "./inputs.js";
|
|
8
|
+
export { getFailureLabels, getStartLabels, getStartRemoveLabels, getSuccessLabels, labelCommands, } from "./labels.js";
|
|
9
|
+
export { formatMultiOutputs, formatOutputs, formatSummary, outputCommands, } from "./outputs.js";
|
|
10
|
+
export { detectTrigger } from "./triggers.js";
|
|
11
|
+
export { CI_DEFAULTS, COMMENT_TRIGGER_PATTERN, LIFECYCLE_LABELS, TRIGGER_LABELS, } from "./types.js";
|
|
12
|
+
export type { ActionInputs, ActionOutputs, CIConfig, GitHubContext, GitHubEventPayload, TriggerResult, TriggerType, } from "./types.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CI integration module — provides everything needed to run sequant
|
|
3
|
+
* workflows from GitHub Actions or other CI/CD systems.
|
|
4
|
+
*/
|
|
5
|
+
export { loadCIConfig, resolveConfig } from "./config.js";
|
|
6
|
+
export { parseInputs, validateInputs } from "./inputs.js";
|
|
7
|
+
export { getFailureLabels, getStartLabels, getStartRemoveLabels, getSuccessLabels, labelCommands, } from "./labels.js";
|
|
8
|
+
export { formatMultiOutputs, formatOutputs, formatSummary, outputCommands, } from "./outputs.js";
|
|
9
|
+
export { detectTrigger } from "./triggers.js";
|
|
10
|
+
export { CI_DEFAULTS, COMMENT_TRIGGER_PATTERN, LIFECYCLE_LABELS, TRIGGER_LABELS, } from "./types.js";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input parsing and validation for the GitHub Action.
|
|
3
|
+
*
|
|
4
|
+
* Parses raw string inputs from action.yml into validated ActionInputs,
|
|
5
|
+
* applying defaults and merging with repo-level configuration.
|
|
6
|
+
*/
|
|
7
|
+
import type { ActionInputs, CIConfig } from "./types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Raw inputs as received from GitHub Actions (all strings).
|
|
10
|
+
*/
|
|
11
|
+
export interface RawActionInputs {
|
|
12
|
+
issues?: string;
|
|
13
|
+
phases?: string;
|
|
14
|
+
agent?: string;
|
|
15
|
+
timeout?: string;
|
|
16
|
+
"quality-loop"?: string;
|
|
17
|
+
"api-key"?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Parse and validate action inputs, merging with repo config.
|
|
21
|
+
*
|
|
22
|
+
* Merge precedence: workflow inputs > config file > action defaults
|
|
23
|
+
*/
|
|
24
|
+
export declare function parseInputs(raw: RawActionInputs, config?: CIConfig): ActionInputs;
|
|
25
|
+
/**
|
|
26
|
+
* Validate that required inputs are present and well-formed.
|
|
27
|
+
* Returns an array of error messages (empty = valid).
|
|
28
|
+
*/
|
|
29
|
+
export declare function validateInputs(inputs: ActionInputs): string[];
|