sequant 1.13.5 → 1.14.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/plugin.json +1 -1
- package/dist/bin/cli.js +1 -0
- package/dist/src/commands/run.d.ts +21 -0
- package/dist/src/commands/run.js +51 -1
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.js +2 -0
- package/dist/src/lib/cli-ui.d.ts +1 -1
- package/dist/src/lib/cli-ui.js +12 -14
- package/dist/src/lib/scope/analyzer.d.ts +91 -0
- package/dist/src/lib/scope/analyzer.js +310 -0
- package/dist/src/lib/scope/formatter.d.ts +52 -0
- package/dist/src/lib/scope/formatter.js +169 -0
- package/dist/src/lib/scope/index.d.ts +36 -0
- package/dist/src/lib/scope/index.js +73 -0
- package/dist/src/lib/scope/types.d.ts +198 -0
- package/dist/src/lib/scope/types.js +60 -0
- package/dist/src/lib/scope/verdict.d.ts +80 -0
- package/dist/src/lib/scope/verdict.js +173 -0
- package/dist/src/lib/settings.d.ts +33 -0
- package/dist/src/lib/settings.js +21 -0
- package/dist/src/lib/workflow/phase-detection.d.ts +114 -0
- package/dist/src/lib/workflow/phase-detection.js +215 -0
- package/dist/src/lib/workflow/state-manager.d.ts +11 -0
- package/dist/src/lib/workflow/state-manager.js +26 -0
- package/dist/src/lib/workflow/state-schema.d.ts +103 -0
- package/dist/src/lib/workflow/state-schema.js +20 -0
- package/package.json +1 -1
- package/templates/skills/exec/SKILL.md +50 -1
- package/templates/skills/fullsolve/SKILL.md +47 -1
- package/templates/skills/qa/SKILL.md +47 -1
- package/templates/skills/security-review/SKILL.md +0 -1
- package/templates/skills/spec/SKILL.md +39 -1
- package/templates/skills/testgen/SKILL.md +1 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope Assessment Formatter
|
|
3
|
+
*
|
|
4
|
+
* Formats scope assessment results as markdown for /spec output
|
|
5
|
+
* and GitHub issue comments.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { formatScopeAssessment } from './formatter';
|
|
10
|
+
*
|
|
11
|
+
* const assessment = performScopeAssessment(criteria, issueBody, title);
|
|
12
|
+
* console.log(formatScopeAssessment(assessment));
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
import { getVerdictEmoji, getStatusEmoji } from "./verdict.js";
|
|
16
|
+
/**
|
|
17
|
+
* Format non-goals section for output
|
|
18
|
+
*
|
|
19
|
+
* @param nonGoals - Non-goals parsing result
|
|
20
|
+
* @returns Formatted markdown string
|
|
21
|
+
*/
|
|
22
|
+
export function formatNonGoals(nonGoals) {
|
|
23
|
+
const lines = ["### Non-Goals (Required)", ""];
|
|
24
|
+
if (!nonGoals.found) {
|
|
25
|
+
lines.push("⚠️ **Non-Goals section not found.** Consider adding scope boundaries.");
|
|
26
|
+
lines.push("");
|
|
27
|
+
lines.push("Example format:");
|
|
28
|
+
lines.push("```markdown");
|
|
29
|
+
lines.push("## Non-Goals");
|
|
30
|
+
lines.push("");
|
|
31
|
+
lines.push("What this issue explicitly will NOT do:");
|
|
32
|
+
lines.push("- [ ] [Adjacent feature we're deferring]");
|
|
33
|
+
lines.push("- [ ] [Scope boundary we're respecting]");
|
|
34
|
+
lines.push("- [ ] [Future work that's out of scope]");
|
|
35
|
+
lines.push("```");
|
|
36
|
+
return lines.join("\n");
|
|
37
|
+
}
|
|
38
|
+
if (nonGoals.items.length === 0) {
|
|
39
|
+
lines.push("⚠️ **Non-Goals section is empty.** Add explicit scope boundaries.");
|
|
40
|
+
return lines.join("\n");
|
|
41
|
+
}
|
|
42
|
+
lines.push(`✅ ${nonGoals.items.length} non-goal(s) defined:`);
|
|
43
|
+
lines.push("");
|
|
44
|
+
for (const item of nonGoals.items) {
|
|
45
|
+
lines.push(`- ${item}`);
|
|
46
|
+
}
|
|
47
|
+
return lines.join("\n");
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Format scope metrics table
|
|
51
|
+
*
|
|
52
|
+
* @param assessment - Complete scope assessment
|
|
53
|
+
* @returns Formatted markdown table
|
|
54
|
+
*/
|
|
55
|
+
export function formatMetricsTable(assessment) {
|
|
56
|
+
const lines = [
|
|
57
|
+
"### Scope Metrics",
|
|
58
|
+
"",
|
|
59
|
+
"| Metric | Value | Status |",
|
|
60
|
+
"|--------|-------|--------|",
|
|
61
|
+
];
|
|
62
|
+
for (const metric of assessment.metrics) {
|
|
63
|
+
const emoji = getStatusEmoji(metric.status);
|
|
64
|
+
lines.push(`| ${metric.name} | ${metric.value} | ${emoji} |`);
|
|
65
|
+
}
|
|
66
|
+
return lines.join("\n");
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Format scope verdict section
|
|
70
|
+
*
|
|
71
|
+
* @param assessment - Complete scope assessment
|
|
72
|
+
* @returns Formatted markdown string
|
|
73
|
+
*/
|
|
74
|
+
export function formatVerdict(assessment) {
|
|
75
|
+
const emoji = getVerdictEmoji(assessment.verdict);
|
|
76
|
+
const lines = [
|
|
77
|
+
"### Scope Verdict",
|
|
78
|
+
"",
|
|
79
|
+
`${emoji} **${assessment.verdict}** - ${assessment.recommendation}`,
|
|
80
|
+
];
|
|
81
|
+
// Add split recommendation details if needed
|
|
82
|
+
if (assessment.verdict === "SCOPE_SPLIT_RECOMMENDED") {
|
|
83
|
+
lines.push("");
|
|
84
|
+
lines.push("**Suggested splits:**");
|
|
85
|
+
// Group ACs by cluster for split suggestions
|
|
86
|
+
const significantClusters = assessment.featureDetection.clusters.filter((c) => c.count >= 2);
|
|
87
|
+
if (significantClusters.length > 1) {
|
|
88
|
+
for (const cluster of significantClusters) {
|
|
89
|
+
lines.push(`- Issue for **${cluster.keyword}**: ${cluster.acIds.join(", ")}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
lines.push("- Consider separating by functional area");
|
|
94
|
+
lines.push("- Each issue should have a single deployable outcome");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return lines.join("\n");
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Format complete scope assessment section
|
|
101
|
+
*
|
|
102
|
+
* @param assessment - Complete scope assessment
|
|
103
|
+
* @returns Formatted markdown string
|
|
104
|
+
*/
|
|
105
|
+
export function formatScopeAssessment(assessment) {
|
|
106
|
+
if (assessment.skipped) {
|
|
107
|
+
return [
|
|
108
|
+
"## Scope Assessment",
|
|
109
|
+
"",
|
|
110
|
+
`*Skipped: ${assessment.skipReason}*`,
|
|
111
|
+
].join("\n");
|
|
112
|
+
}
|
|
113
|
+
const sections = [
|
|
114
|
+
"## Scope Assessment",
|
|
115
|
+
"",
|
|
116
|
+
formatNonGoals(assessment.nonGoals),
|
|
117
|
+
"",
|
|
118
|
+
formatMetricsTable(assessment),
|
|
119
|
+
"",
|
|
120
|
+
formatVerdict(assessment),
|
|
121
|
+
];
|
|
122
|
+
// Add quality loop recommendation if needed
|
|
123
|
+
if (assessment.verdict === "SCOPE_WARNING" ||
|
|
124
|
+
assessment.verdict === "SCOPE_SPLIT_RECOMMENDED") {
|
|
125
|
+
sections.push("");
|
|
126
|
+
sections.push("---");
|
|
127
|
+
sections.push("");
|
|
128
|
+
sections.push("**Quality Loop:** Will be auto-enabled due to scope concerns.");
|
|
129
|
+
}
|
|
130
|
+
return sections.join("\n");
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Format condensed scope assessment for issue comments
|
|
134
|
+
*
|
|
135
|
+
* A shorter version suitable for GitHub issue comments.
|
|
136
|
+
*
|
|
137
|
+
* @param assessment - Complete scope assessment
|
|
138
|
+
* @returns Condensed markdown string
|
|
139
|
+
*/
|
|
140
|
+
export function formatCondensedAssessment(assessment) {
|
|
141
|
+
if (assessment.skipped) {
|
|
142
|
+
return `**Scope Assessment:** Skipped (${assessment.skipReason})`;
|
|
143
|
+
}
|
|
144
|
+
const emoji = getVerdictEmoji(assessment.verdict);
|
|
145
|
+
const lines = [
|
|
146
|
+
`### Scope Assessment: ${emoji} ${assessment.verdict}`,
|
|
147
|
+
];
|
|
148
|
+
// Metrics in single line
|
|
149
|
+
const metricsLine = assessment.metrics
|
|
150
|
+
.map((m) => `${m.name}: ${m.value} ${getStatusEmoji(m.status)}`)
|
|
151
|
+
.join(" | ");
|
|
152
|
+
lines.push("");
|
|
153
|
+
lines.push(metricsLine);
|
|
154
|
+
// Non-goals summary
|
|
155
|
+
if (!assessment.nonGoals.found || assessment.nonGoals.items.length === 0) {
|
|
156
|
+
lines.push("");
|
|
157
|
+
lines.push("⚠️ Non-Goals: Not defined");
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
lines.push("");
|
|
161
|
+
lines.push(`✅ Non-Goals: ${assessment.nonGoals.items.length} defined`);
|
|
162
|
+
}
|
|
163
|
+
// Recommendation if not OK
|
|
164
|
+
if (assessment.verdict !== "SCOPE_OK") {
|
|
165
|
+
lines.push("");
|
|
166
|
+
lines.push(`*${assessment.recommendation}*`);
|
|
167
|
+
}
|
|
168
|
+
return lines.join("\n");
|
|
169
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope Assessment Module
|
|
3
|
+
*
|
|
4
|
+
* Provides scope analysis for /spec to catch overscoped issues early.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { performScopeAssessment, formatScopeAssessment } from './scope';
|
|
9
|
+
*
|
|
10
|
+
* const criteria = parseAcceptanceCriteria(issueBody);
|
|
11
|
+
* const assessment = performScopeAssessment(criteria, issueBody, issueTitle);
|
|
12
|
+
*
|
|
13
|
+
* if (assessment.verdict !== 'SCOPE_OK') {
|
|
14
|
+
* console.log(formatScopeAssessment(assessment));
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
import type { AcceptanceCriterion } from "../workflow/state-schema.js";
|
|
19
|
+
import type { ScopeAssessment, ScopeAssessmentConfig } from "./types.js";
|
|
20
|
+
export type { ScopeVerdict, ScopeMetricStatus, ScopeMetric, ScopeAssessment, ScopeAssessmentConfig, MetricThreshold, ACCluster, FeatureDetection, NonGoals, } from "./types.js";
|
|
21
|
+
export { DEFAULT_SCOPE_CONFIG, ScopeAssessmentSchema } from "./types.js";
|
|
22
|
+
export { clusterACByKeyword, detectTitleVerbs, estimateDirectorySpread, calculateFeatureCount, detectFeatures, parseNonGoals, shouldSkipAssessment, } from "./analyzer.js";
|
|
23
|
+
export { getMetricStatus, createScopeMetrics, calculateVerdict, generateRecommendation, getVerdictEmoji, getStatusEmoji, shouldEnableQualityLoop, } from "./verdict.js";
|
|
24
|
+
export { formatNonGoals, formatMetricsTable, formatVerdict, formatScopeAssessment, formatCondensedAssessment, } from "./formatter.js";
|
|
25
|
+
/**
|
|
26
|
+
* Perform complete scope assessment
|
|
27
|
+
*
|
|
28
|
+
* This is the main entry point for scope assessment.
|
|
29
|
+
*
|
|
30
|
+
* @param criteria - Parsed acceptance criteria
|
|
31
|
+
* @param issueBody - Full issue body markdown
|
|
32
|
+
* @param title - Issue title
|
|
33
|
+
* @param config - Optional custom configuration
|
|
34
|
+
* @returns Complete scope assessment result
|
|
35
|
+
*/
|
|
36
|
+
export declare function performScopeAssessment(criteria: AcceptanceCriterion[], issueBody: string, title: string, config?: ScopeAssessmentConfig): ScopeAssessment;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope Assessment Module
|
|
3
|
+
*
|
|
4
|
+
* Provides scope analysis for /spec to catch overscoped issues early.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { performScopeAssessment, formatScopeAssessment } from './scope';
|
|
9
|
+
*
|
|
10
|
+
* const criteria = parseAcceptanceCriteria(issueBody);
|
|
11
|
+
* const assessment = performScopeAssessment(criteria, issueBody, issueTitle);
|
|
12
|
+
*
|
|
13
|
+
* if (assessment.verdict !== 'SCOPE_OK') {
|
|
14
|
+
* console.log(formatScopeAssessment(assessment));
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
import { DEFAULT_SCOPE_CONFIG } from "./types.js";
|
|
19
|
+
import { detectFeatures, parseNonGoals, shouldSkipAssessment, } from "./analyzer.js";
|
|
20
|
+
import { createScopeMetrics, calculateVerdict, generateRecommendation, } from "./verdict.js";
|
|
21
|
+
export { DEFAULT_SCOPE_CONFIG, ScopeAssessmentSchema } from "./types.js";
|
|
22
|
+
// Re-export analyzer functions
|
|
23
|
+
export { clusterACByKeyword, detectTitleVerbs, estimateDirectorySpread, calculateFeatureCount, detectFeatures, parseNonGoals, shouldSkipAssessment, } from "./analyzer.js";
|
|
24
|
+
// Re-export verdict functions
|
|
25
|
+
export { getMetricStatus, createScopeMetrics, calculateVerdict, generateRecommendation, getVerdictEmoji, getStatusEmoji, shouldEnableQualityLoop, } from "./verdict.js";
|
|
26
|
+
// Re-export formatter functions
|
|
27
|
+
export { formatNonGoals, formatMetricsTable, formatVerdict, formatScopeAssessment, formatCondensedAssessment, } from "./formatter.js";
|
|
28
|
+
/**
|
|
29
|
+
* Perform complete scope assessment
|
|
30
|
+
*
|
|
31
|
+
* This is the main entry point for scope assessment.
|
|
32
|
+
*
|
|
33
|
+
* @param criteria - Parsed acceptance criteria
|
|
34
|
+
* @param issueBody - Full issue body markdown
|
|
35
|
+
* @param title - Issue title
|
|
36
|
+
* @param config - Optional custom configuration
|
|
37
|
+
* @returns Complete scope assessment result
|
|
38
|
+
*/
|
|
39
|
+
export function performScopeAssessment(criteria, issueBody, title, config = DEFAULT_SCOPE_CONFIG) {
|
|
40
|
+
// Parse non-goals first
|
|
41
|
+
const nonGoals = parseNonGoals(issueBody);
|
|
42
|
+
// Detect features
|
|
43
|
+
const featureDetection = detectFeatures(criteria, title);
|
|
44
|
+
// Check if we should skip (trivial issue)
|
|
45
|
+
const skipResult = shouldSkipAssessment(criteria.length, featureDetection.directorySpread, config);
|
|
46
|
+
if (skipResult.skip) {
|
|
47
|
+
return {
|
|
48
|
+
assessedAt: new Date().toISOString(),
|
|
49
|
+
skipped: true,
|
|
50
|
+
skipReason: skipResult.reason,
|
|
51
|
+
verdict: "SCOPE_OK",
|
|
52
|
+
metrics: [],
|
|
53
|
+
featureDetection,
|
|
54
|
+
nonGoals,
|
|
55
|
+
recommendation: "Trivial issue - scope assessment skipped.",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
// Calculate metrics
|
|
59
|
+
const metrics = createScopeMetrics(featureDetection, criteria.length, config);
|
|
60
|
+
// Determine verdict
|
|
61
|
+
const verdict = calculateVerdict(metrics, nonGoals);
|
|
62
|
+
// Generate recommendation
|
|
63
|
+
const recommendation = generateRecommendation(verdict, metrics, featureDetection, nonGoals);
|
|
64
|
+
return {
|
|
65
|
+
assessedAt: new Date().toISOString(),
|
|
66
|
+
skipped: false,
|
|
67
|
+
verdict,
|
|
68
|
+
metrics,
|
|
69
|
+
featureDetection,
|
|
70
|
+
nonGoals,
|
|
71
|
+
recommendation,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope Assessment Types
|
|
3
|
+
*
|
|
4
|
+
* Types for the scope assessment system that evaluates issue scope
|
|
5
|
+
* during the /spec phase to catch overscoped issues early.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
/**
|
|
9
|
+
* Scope verdict - overall assessment result
|
|
10
|
+
*/
|
|
11
|
+
export type ScopeVerdict = "SCOPE_OK" | "SCOPE_WARNING" | "SCOPE_SPLIT_RECOMMENDED";
|
|
12
|
+
/**
|
|
13
|
+
* Scope metric status based on thresholds
|
|
14
|
+
*/
|
|
15
|
+
export type ScopeMetricStatus = "green" | "yellow" | "red";
|
|
16
|
+
/**
|
|
17
|
+
* Threshold configuration for a single metric
|
|
18
|
+
*/
|
|
19
|
+
export interface MetricThreshold {
|
|
20
|
+
/** Value at which status becomes yellow */
|
|
21
|
+
yellow: number;
|
|
22
|
+
/** Value at which status becomes red */
|
|
23
|
+
red: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Scope assessment configuration
|
|
27
|
+
*/
|
|
28
|
+
export interface ScopeAssessmentConfig {
|
|
29
|
+
/** Whether scope assessment is enabled */
|
|
30
|
+
enabled: boolean;
|
|
31
|
+
/** Skip assessment for trivial issues */
|
|
32
|
+
skipIfSimple: boolean;
|
|
33
|
+
/** Trivial issue thresholds (skip if below all) */
|
|
34
|
+
trivialThresholds: {
|
|
35
|
+
/** Maximum AC items for trivial classification */
|
|
36
|
+
maxACItems: number;
|
|
37
|
+
/** Maximum directories touched for trivial classification */
|
|
38
|
+
maxDirectories: number;
|
|
39
|
+
};
|
|
40
|
+
/** Thresholds for scope metrics */
|
|
41
|
+
thresholds: {
|
|
42
|
+
/** Feature count thresholds */
|
|
43
|
+
featureCount: MetricThreshold;
|
|
44
|
+
/** AC items thresholds */
|
|
45
|
+
acItems: MetricThreshold;
|
|
46
|
+
/** File estimate thresholds */
|
|
47
|
+
fileEstimate: MetricThreshold;
|
|
48
|
+
/** Directory spread thresholds */
|
|
49
|
+
directorySpread: MetricThreshold;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Default scope assessment configuration
|
|
54
|
+
*/
|
|
55
|
+
export declare const DEFAULT_SCOPE_CONFIG: ScopeAssessmentConfig;
|
|
56
|
+
/**
|
|
57
|
+
* Individual scope metric
|
|
58
|
+
*/
|
|
59
|
+
export interface ScopeMetric {
|
|
60
|
+
/** Name of the metric */
|
|
61
|
+
name: string;
|
|
62
|
+
/** Current value */
|
|
63
|
+
value: number;
|
|
64
|
+
/** Status based on thresholds */
|
|
65
|
+
status: ScopeMetricStatus;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Clustering result for AC items
|
|
69
|
+
*/
|
|
70
|
+
export interface ACCluster {
|
|
71
|
+
/** Cluster identifier (keyword-based) */
|
|
72
|
+
keyword: string;
|
|
73
|
+
/** AC IDs in this cluster */
|
|
74
|
+
acIds: string[];
|
|
75
|
+
/** Number of AC items in cluster */
|
|
76
|
+
count: number;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Feature detection result
|
|
80
|
+
*/
|
|
81
|
+
export interface FeatureDetection {
|
|
82
|
+
/** Number of distinct features detected */
|
|
83
|
+
featureCount: number;
|
|
84
|
+
/** AC clusters by functional area */
|
|
85
|
+
clusters: ACCluster[];
|
|
86
|
+
/** Multiple verbs detected in title */
|
|
87
|
+
multipleVerbs: boolean;
|
|
88
|
+
/** Detected verb list from title */
|
|
89
|
+
titleVerbs: string[];
|
|
90
|
+
/** Directory spread detected */
|
|
91
|
+
directorySpread: number;
|
|
92
|
+
/** Detected directories from analysis */
|
|
93
|
+
directories: string[];
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Non-goals section data
|
|
97
|
+
*/
|
|
98
|
+
export interface NonGoals {
|
|
99
|
+
/** List of non-goal items */
|
|
100
|
+
items: string[];
|
|
101
|
+
/** Whether non-goals section was found */
|
|
102
|
+
found: boolean;
|
|
103
|
+
/** Warning message if empty or missing */
|
|
104
|
+
warning?: string;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Complete scope assessment result
|
|
108
|
+
*/
|
|
109
|
+
export interface ScopeAssessment {
|
|
110
|
+
/** Timestamp of assessment */
|
|
111
|
+
assessedAt: string;
|
|
112
|
+
/** Whether assessment was skipped (trivial issue) */
|
|
113
|
+
skipped: boolean;
|
|
114
|
+
/** Reason for skipping (if skipped) */
|
|
115
|
+
skipReason?: string;
|
|
116
|
+
/** Overall verdict */
|
|
117
|
+
verdict: ScopeVerdict;
|
|
118
|
+
/** Individual metrics */
|
|
119
|
+
metrics: ScopeMetric[];
|
|
120
|
+
/** Feature detection details */
|
|
121
|
+
featureDetection: FeatureDetection;
|
|
122
|
+
/** Non-goals section */
|
|
123
|
+
nonGoals: NonGoals;
|
|
124
|
+
/** Recommendation message */
|
|
125
|
+
recommendation: string;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Zod schema for scope assessment (for state storage)
|
|
129
|
+
*/
|
|
130
|
+
export declare const ScopeMetricSchema: z.ZodObject<{
|
|
131
|
+
name: z.ZodString;
|
|
132
|
+
value: z.ZodNumber;
|
|
133
|
+
status: z.ZodEnum<{
|
|
134
|
+
red: "red";
|
|
135
|
+
green: "green";
|
|
136
|
+
yellow: "yellow";
|
|
137
|
+
}>;
|
|
138
|
+
}, z.core.$strip>;
|
|
139
|
+
export declare const ACClusterSchema: z.ZodObject<{
|
|
140
|
+
keyword: z.ZodString;
|
|
141
|
+
acIds: z.ZodArray<z.ZodString>;
|
|
142
|
+
count: z.ZodNumber;
|
|
143
|
+
}, z.core.$strip>;
|
|
144
|
+
export declare const FeatureDetectionSchema: z.ZodObject<{
|
|
145
|
+
featureCount: z.ZodNumber;
|
|
146
|
+
clusters: z.ZodArray<z.ZodObject<{
|
|
147
|
+
keyword: z.ZodString;
|
|
148
|
+
acIds: z.ZodArray<z.ZodString>;
|
|
149
|
+
count: z.ZodNumber;
|
|
150
|
+
}, z.core.$strip>>;
|
|
151
|
+
multipleVerbs: z.ZodBoolean;
|
|
152
|
+
titleVerbs: z.ZodArray<z.ZodString>;
|
|
153
|
+
directorySpread: z.ZodNumber;
|
|
154
|
+
directories: z.ZodArray<z.ZodString>;
|
|
155
|
+
}, z.core.$strip>;
|
|
156
|
+
export declare const NonGoalsSchema: z.ZodObject<{
|
|
157
|
+
items: z.ZodArray<z.ZodString>;
|
|
158
|
+
found: z.ZodBoolean;
|
|
159
|
+
warning: z.ZodOptional<z.ZodString>;
|
|
160
|
+
}, z.core.$strip>;
|
|
161
|
+
export declare const ScopeAssessmentSchema: z.ZodObject<{
|
|
162
|
+
assessedAt: z.ZodString;
|
|
163
|
+
skipped: z.ZodBoolean;
|
|
164
|
+
skipReason: z.ZodOptional<z.ZodString>;
|
|
165
|
+
verdict: z.ZodEnum<{
|
|
166
|
+
SCOPE_OK: "SCOPE_OK";
|
|
167
|
+
SCOPE_WARNING: "SCOPE_WARNING";
|
|
168
|
+
SCOPE_SPLIT_RECOMMENDED: "SCOPE_SPLIT_RECOMMENDED";
|
|
169
|
+
}>;
|
|
170
|
+
metrics: z.ZodArray<z.ZodObject<{
|
|
171
|
+
name: z.ZodString;
|
|
172
|
+
value: z.ZodNumber;
|
|
173
|
+
status: z.ZodEnum<{
|
|
174
|
+
red: "red";
|
|
175
|
+
green: "green";
|
|
176
|
+
yellow: "yellow";
|
|
177
|
+
}>;
|
|
178
|
+
}, z.core.$strip>>;
|
|
179
|
+
featureDetection: z.ZodObject<{
|
|
180
|
+
featureCount: z.ZodNumber;
|
|
181
|
+
clusters: z.ZodArray<z.ZodObject<{
|
|
182
|
+
keyword: z.ZodString;
|
|
183
|
+
acIds: z.ZodArray<z.ZodString>;
|
|
184
|
+
count: z.ZodNumber;
|
|
185
|
+
}, z.core.$strip>>;
|
|
186
|
+
multipleVerbs: z.ZodBoolean;
|
|
187
|
+
titleVerbs: z.ZodArray<z.ZodString>;
|
|
188
|
+
directorySpread: z.ZodNumber;
|
|
189
|
+
directories: z.ZodArray<z.ZodString>;
|
|
190
|
+
}, z.core.$strip>;
|
|
191
|
+
nonGoals: z.ZodObject<{
|
|
192
|
+
items: z.ZodArray<z.ZodString>;
|
|
193
|
+
found: z.ZodBoolean;
|
|
194
|
+
warning: z.ZodOptional<z.ZodString>;
|
|
195
|
+
}, z.core.$strip>;
|
|
196
|
+
recommendation: z.ZodString;
|
|
197
|
+
}, z.core.$strip>;
|
|
198
|
+
export type ScopeMetricType = z.infer<typeof ScopeMetricSchema>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope Assessment Types
|
|
3
|
+
*
|
|
4
|
+
* Types for the scope assessment system that evaluates issue scope
|
|
5
|
+
* during the /spec phase to catch overscoped issues early.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
/**
|
|
9
|
+
* Default scope assessment configuration
|
|
10
|
+
*/
|
|
11
|
+
export const DEFAULT_SCOPE_CONFIG = {
|
|
12
|
+
enabled: true,
|
|
13
|
+
skipIfSimple: true,
|
|
14
|
+
trivialThresholds: {
|
|
15
|
+
maxACItems: 3,
|
|
16
|
+
maxDirectories: 1,
|
|
17
|
+
},
|
|
18
|
+
thresholds: {
|
|
19
|
+
featureCount: { yellow: 2, red: 3 },
|
|
20
|
+
acItems: { yellow: 6, red: 9 },
|
|
21
|
+
fileEstimate: { yellow: 8, red: 13 },
|
|
22
|
+
directorySpread: { yellow: 3, red: 5 },
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Zod schema for scope assessment (for state storage)
|
|
27
|
+
*/
|
|
28
|
+
export const ScopeMetricSchema = z.object({
|
|
29
|
+
name: z.string(),
|
|
30
|
+
value: z.number(),
|
|
31
|
+
status: z.enum(["green", "yellow", "red"]),
|
|
32
|
+
});
|
|
33
|
+
export const ACClusterSchema = z.object({
|
|
34
|
+
keyword: z.string(),
|
|
35
|
+
acIds: z.array(z.string()),
|
|
36
|
+
count: z.number(),
|
|
37
|
+
});
|
|
38
|
+
export const FeatureDetectionSchema = z.object({
|
|
39
|
+
featureCount: z.number(),
|
|
40
|
+
clusters: z.array(ACClusterSchema),
|
|
41
|
+
multipleVerbs: z.boolean(),
|
|
42
|
+
titleVerbs: z.array(z.string()),
|
|
43
|
+
directorySpread: z.number(),
|
|
44
|
+
directories: z.array(z.string()),
|
|
45
|
+
});
|
|
46
|
+
export const NonGoalsSchema = z.object({
|
|
47
|
+
items: z.array(z.string()),
|
|
48
|
+
found: z.boolean(),
|
|
49
|
+
warning: z.string().optional(),
|
|
50
|
+
});
|
|
51
|
+
export const ScopeAssessmentSchema = z.object({
|
|
52
|
+
assessedAt: z.string().datetime(),
|
|
53
|
+
skipped: z.boolean(),
|
|
54
|
+
skipReason: z.string().optional(),
|
|
55
|
+
verdict: z.enum(["SCOPE_OK", "SCOPE_WARNING", "SCOPE_SPLIT_RECOMMENDED"]),
|
|
56
|
+
metrics: z.array(ScopeMetricSchema),
|
|
57
|
+
featureDetection: FeatureDetectionSchema,
|
|
58
|
+
nonGoals: NonGoalsSchema,
|
|
59
|
+
recommendation: z.string(),
|
|
60
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope Verdict Calculator
|
|
3
|
+
*
|
|
4
|
+
* Determines scope verdict based on metrics and thresholds.
|
|
5
|
+
* Provides recommendations for scope improvement.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { calculateVerdict, getMetricStatus } from './verdict';
|
|
10
|
+
*
|
|
11
|
+
* const metrics = [
|
|
12
|
+
* { name: 'featureCount', value: 2, status: 'yellow' },
|
|
13
|
+
* { name: 'acItems', value: 8, status: 'yellow' },
|
|
14
|
+
* ];
|
|
15
|
+
* const verdict = calculateVerdict(metrics);
|
|
16
|
+
* // Returns: 'SCOPE_WARNING'
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
import type { ScopeMetric, ScopeMetricStatus, ScopeVerdict, ScopeAssessmentConfig, MetricThreshold, FeatureDetection, NonGoals } from "./types.js";
|
|
20
|
+
/**
|
|
21
|
+
* Get status for a metric value based on thresholds
|
|
22
|
+
*
|
|
23
|
+
* @param value - Current metric value
|
|
24
|
+
* @param threshold - Threshold configuration
|
|
25
|
+
* @returns Metric status (green/yellow/red)
|
|
26
|
+
*/
|
|
27
|
+
export declare function getMetricStatus(value: number, threshold: MetricThreshold): ScopeMetricStatus;
|
|
28
|
+
/**
|
|
29
|
+
* Create scope metrics from feature detection result
|
|
30
|
+
*
|
|
31
|
+
* @param detection - Feature detection result
|
|
32
|
+
* @param acCount - Total AC items count
|
|
33
|
+
* @param config - Scope assessment configuration
|
|
34
|
+
* @returns Array of scope metrics with status
|
|
35
|
+
*/
|
|
36
|
+
export declare function createScopeMetrics(detection: FeatureDetection, acCount: number, config?: ScopeAssessmentConfig): ScopeMetric[];
|
|
37
|
+
/**
|
|
38
|
+
* Calculate overall verdict from metrics
|
|
39
|
+
*
|
|
40
|
+
* Logic:
|
|
41
|
+
* - Any red metric → SCOPE_SPLIT_RECOMMENDED
|
|
42
|
+
* - Any yellow metric → SCOPE_WARNING
|
|
43
|
+
* - All green → SCOPE_OK
|
|
44
|
+
*
|
|
45
|
+
* @param metrics - Array of scope metrics
|
|
46
|
+
* @param nonGoals - Non-goals parsing result
|
|
47
|
+
* @returns Overall scope verdict
|
|
48
|
+
*/
|
|
49
|
+
export declare function calculateVerdict(metrics: ScopeMetric[], nonGoals: NonGoals): ScopeVerdict;
|
|
50
|
+
/**
|
|
51
|
+
* Generate recommendation message based on verdict and metrics
|
|
52
|
+
*
|
|
53
|
+
* @param verdict - Calculated verdict
|
|
54
|
+
* @param metrics - Scope metrics
|
|
55
|
+
* @param detection - Feature detection result
|
|
56
|
+
* @param nonGoals - Non-goals parsing result
|
|
57
|
+
* @returns Recommendation message
|
|
58
|
+
*/
|
|
59
|
+
export declare function generateRecommendation(verdict: ScopeVerdict, metrics: ScopeMetric[], detection: FeatureDetection, nonGoals: NonGoals): string;
|
|
60
|
+
/**
|
|
61
|
+
* Get emoji indicator for verdict
|
|
62
|
+
*
|
|
63
|
+
* @param verdict - Scope verdict
|
|
64
|
+
* @returns Emoji string
|
|
65
|
+
*/
|
|
66
|
+
export declare function getVerdictEmoji(verdict: ScopeVerdict): string;
|
|
67
|
+
/**
|
|
68
|
+
* Get emoji indicator for metric status
|
|
69
|
+
*
|
|
70
|
+
* @param status - Metric status
|
|
71
|
+
* @returns Emoji string
|
|
72
|
+
*/
|
|
73
|
+
export declare function getStatusEmoji(status: ScopeMetricStatus): string;
|
|
74
|
+
/**
|
|
75
|
+
* Check if quality loop should be auto-enabled based on verdict
|
|
76
|
+
*
|
|
77
|
+
* @param verdict - Scope verdict
|
|
78
|
+
* @returns Whether to enable quality loop
|
|
79
|
+
*/
|
|
80
|
+
export declare function shouldEnableQualityLoop(verdict: ScopeVerdict): boolean;
|