sequant 1.11.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +93 -7
  2. package/dist/bin/cli.js +12 -9
  3. package/dist/src/commands/doctor.js +25 -20
  4. package/dist/src/commands/init.js +152 -65
  5. package/dist/src/commands/logs.js +7 -6
  6. package/dist/src/commands/run.d.ts +13 -1
  7. package/dist/src/commands/run.js +75 -12
  8. package/dist/src/commands/stats.js +67 -48
  9. package/dist/src/commands/status.js +30 -12
  10. package/dist/src/index.d.ts +6 -0
  11. package/dist/src/index.js +4 -0
  12. package/dist/src/lib/ac-linter.d.ts +116 -0
  13. package/dist/src/lib/ac-linter.js +304 -0
  14. package/dist/src/lib/cli-ui.d.ts +196 -0
  15. package/dist/src/lib/cli-ui.js +544 -0
  16. package/dist/src/lib/content-analyzer.d.ts +89 -0
  17. package/dist/src/lib/content-analyzer.js +437 -0
  18. package/dist/src/lib/phase-signal.d.ts +94 -0
  19. package/dist/src/lib/phase-signal.js +171 -0
  20. package/dist/src/lib/plugin-version-sync.d.ts +26 -0
  21. package/dist/src/lib/plugin-version-sync.js +91 -0
  22. package/dist/src/lib/project-name.d.ts +40 -0
  23. package/dist/src/lib/project-name.js +191 -0
  24. package/dist/src/lib/semgrep.d.ts +136 -0
  25. package/dist/src/lib/semgrep.js +406 -0
  26. package/dist/src/lib/solve-comment-parser.d.ts +84 -0
  27. package/dist/src/lib/solve-comment-parser.js +200 -0
  28. package/dist/src/lib/stack-config.d.ts +51 -0
  29. package/dist/src/lib/stack-config.js +77 -0
  30. package/dist/src/lib/stacks.d.ts +66 -0
  31. package/dist/src/lib/stacks.js +332 -0
  32. package/dist/src/lib/templates.d.ts +2 -0
  33. package/dist/src/lib/templates.js +12 -3
  34. package/dist/src/lib/upstream/assessment.d.ts +70 -0
  35. package/dist/src/lib/upstream/assessment.js +385 -0
  36. package/dist/src/lib/upstream/index.d.ts +11 -0
  37. package/dist/src/lib/upstream/index.js +14 -0
  38. package/dist/src/lib/upstream/issues.d.ts +38 -0
  39. package/dist/src/lib/upstream/issues.js +267 -0
  40. package/dist/src/lib/upstream/relevance.d.ts +50 -0
  41. package/dist/src/lib/upstream/relevance.js +209 -0
  42. package/dist/src/lib/upstream/report.d.ts +29 -0
  43. package/dist/src/lib/upstream/report.js +391 -0
  44. package/dist/src/lib/upstream/types.d.ts +207 -0
  45. package/dist/src/lib/upstream/types.js +5 -0
  46. package/dist/src/lib/workflow/log-writer.d.ts +1 -1
  47. package/dist/src/lib/workflow/metrics-schema.d.ts +3 -3
  48. package/dist/src/lib/workflow/qa-cache.d.ts +199 -0
  49. package/dist/src/lib/workflow/qa-cache.js +440 -0
  50. package/dist/src/lib/workflow/run-log-schema.d.ts +34 -6
  51. package/dist/src/lib/workflow/run-log-schema.js +12 -1
  52. package/dist/src/lib/workflow/state-schema.d.ts +4 -4
  53. package/dist/src/lib/workflow/types.d.ts +4 -0
  54. package/package.json +6 -1
  55. package/templates/hooks/pre-tool.sh +6 -0
  56. package/templates/memory/constitution.md +1 -5
  57. package/templates/skills/_shared/references/prompt-templates.md +350 -0
  58. package/templates/skills/_shared/references/subagent-types.md +131 -0
  59. package/templates/skills/exec/SKILL.md +82 -0
  60. package/templates/skills/fullsolve/SKILL.md +19 -2
  61. package/templates/skills/loop/SKILL.md +3 -1
  62. package/templates/skills/qa/SKILL.md +79 -9
  63. package/templates/skills/qa/references/quality-gates.md +85 -1
  64. package/templates/skills/qa/references/semgrep-rules.md +207 -0
  65. package/templates/skills/qa/scripts/quality-checks.sh +525 -15
  66. package/templates/skills/spec/SKILL.md +322 -9
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Plugin version sync utilities
3
+ *
4
+ * Ensures plugin.json version stays in sync with package.json version.
5
+ * Used by CI validation and /release skill.
6
+ */
7
+ export interface VersionSyncResult {
8
+ inSync: boolean;
9
+ packageVersion: string | null;
10
+ pluginVersion: string | null;
11
+ error?: string;
12
+ }
13
+ /**
14
+ * Check if package.json and plugin.json versions are in sync
15
+ *
16
+ * @param projectRoot - Root directory of the project
17
+ * @returns Sync status with version details
18
+ */
19
+ export declare function checkVersionSync(projectRoot?: string): VersionSyncResult;
20
+ /**
21
+ * Get a helpful error message for version mismatch
22
+ *
23
+ * @param result - Version sync result
24
+ * @returns Human-readable error message with fix command
25
+ */
26
+ export declare function getVersionMismatchMessage(result: VersionSyncResult): string;
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Plugin version sync utilities
3
+ *
4
+ * Ensures plugin.json version stays in sync with package.json version.
5
+ * Used by CI validation and /release skill.
6
+ */
7
+ import { existsSync, readFileSync } from "fs";
8
+ import { join } from "path";
9
+ /**
10
+ * Check if package.json and plugin.json versions are in sync
11
+ *
12
+ * @param projectRoot - Root directory of the project
13
+ * @returns Sync status with version details
14
+ */
15
+ export function checkVersionSync(projectRoot = process.cwd()) {
16
+ const packageJsonPath = join(projectRoot, "package.json");
17
+ const pluginJsonPath = join(projectRoot, ".claude-plugin", "plugin.json");
18
+ // Check package.json exists
19
+ if (!existsSync(packageJsonPath)) {
20
+ return {
21
+ inSync: false,
22
+ packageVersion: null,
23
+ pluginVersion: null,
24
+ error: "package.json not found",
25
+ };
26
+ }
27
+ // Check plugin.json exists
28
+ if (!existsSync(pluginJsonPath)) {
29
+ return {
30
+ inSync: false,
31
+ packageVersion: null,
32
+ pluginVersion: null,
33
+ error: "plugin.json not found",
34
+ };
35
+ }
36
+ try {
37
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
38
+ const pluginJson = JSON.parse(readFileSync(pluginJsonPath, "utf8"));
39
+ const packageVersion = packageJson.version || null;
40
+ const pluginVersion = pluginJson.version || null;
41
+ if (!packageVersion) {
42
+ return {
43
+ inSync: false,
44
+ packageVersion: null,
45
+ pluginVersion,
46
+ error: "package.json missing version field",
47
+ };
48
+ }
49
+ if (!pluginVersion) {
50
+ return {
51
+ inSync: false,
52
+ packageVersion,
53
+ pluginVersion: null,
54
+ error: "plugin.json missing version field",
55
+ };
56
+ }
57
+ return {
58
+ inSync: packageVersion === pluginVersion,
59
+ packageVersion,
60
+ pluginVersion,
61
+ };
62
+ }
63
+ catch (e) {
64
+ return {
65
+ inSync: false,
66
+ packageVersion: null,
67
+ pluginVersion: null,
68
+ error: `Failed to parse JSON: ${e instanceof Error ? e.message : String(e)}`,
69
+ };
70
+ }
71
+ }
72
+ /**
73
+ * Get a helpful error message for version mismatch
74
+ *
75
+ * @param result - Version sync result
76
+ * @returns Human-readable error message with fix command
77
+ */
78
+ export function getVersionMismatchMessage(result) {
79
+ if (result.inSync) {
80
+ return `✓ Versions are in sync: ${result.packageVersion}`;
81
+ }
82
+ if (result.error) {
83
+ return `✗ Version sync check failed: ${result.error}`;
84
+ }
85
+ return `✗ Version mismatch!
86
+ package.json: ${result.packageVersion}
87
+ plugin.json: ${result.pluginVersion}
88
+
89
+ Run the following to sync:
90
+ node -e "const p=require('./.claude-plugin/plugin.json');p.version='${result.packageVersion}';require('fs').writeFileSync('./.claude-plugin/plugin.json',JSON.stringify(p,null,2)+'\\n')"`;
91
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Project name detection utility
3
+ *
4
+ * Detects project name from various sources in priority order:
5
+ * 1. package.json → name field
6
+ * 2. Cargo.toml → [package] name
7
+ * 3. pyproject.toml → [project] name OR [tool.poetry] name
8
+ * 4. go.mod → module path (last segment)
9
+ * 5. git remote → extract repo name from origin URL
10
+ * 6. Directory name → basename(cwd) (fallback)
11
+ */
12
+ /**
13
+ * Result of project name detection
14
+ */
15
+ export interface ProjectNameResult {
16
+ /** The detected project name */
17
+ name: string;
18
+ /** The source from which the name was detected */
19
+ source: "package.json" | "Cargo.toml" | "pyproject.toml" | "go.mod" | "git-remote" | "directory";
20
+ }
21
+ /**
22
+ * Detect project name from available sources
23
+ *
24
+ * Tries sources in priority order:
25
+ * 1. package.json
26
+ * 2. Cargo.toml
27
+ * 3. pyproject.toml
28
+ * 4. go.mod
29
+ * 5. git remote
30
+ * 6. directory name
31
+ *
32
+ * @returns The detected project name and its source
33
+ */
34
+ export declare function detectProjectName(): Promise<ProjectNameResult>;
35
+ /**
36
+ * Detect project name, returning just the name string
37
+ *
38
+ * Convenience function for cases where only the name is needed.
39
+ */
40
+ export declare function getProjectName(): Promise<string>;
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Project name detection utility
3
+ *
4
+ * Detects project name from various sources in priority order:
5
+ * 1. package.json → name field
6
+ * 2. Cargo.toml → [package] name
7
+ * 3. pyproject.toml → [project] name OR [tool.poetry] name
8
+ * 4. go.mod → module path (last segment)
9
+ * 5. git remote → extract repo name from origin URL
10
+ * 6. Directory name → basename(cwd) (fallback)
11
+ */
12
+ import { basename } from "path";
13
+ import { spawnSync } from "child_process";
14
+ import { fileExists, readFile } from "./fs.js";
15
+ /**
16
+ * Extract project name from package.json
17
+ */
18
+ async function fromPackageJson() {
19
+ if (!(await fileExists("package.json"))) {
20
+ return null;
21
+ }
22
+ try {
23
+ const content = await readFile("package.json");
24
+ const pkg = JSON.parse(content);
25
+ if (typeof pkg.name === "string" && pkg.name.trim()) {
26
+ return pkg.name.trim();
27
+ }
28
+ }
29
+ catch {
30
+ // Parse error or missing name field
31
+ }
32
+ return null;
33
+ }
34
+ /**
35
+ * Extract project name from Cargo.toml (Rust)
36
+ */
37
+ async function fromCargoToml() {
38
+ if (!(await fileExists("Cargo.toml"))) {
39
+ return null;
40
+ }
41
+ try {
42
+ const content = await readFile("Cargo.toml");
43
+ // Look for: name = "project-name" in [package] section
44
+ // Simple regex approach - handles most common formats
45
+ const match = content.match(/\[package\][\s\S]*?name\s*=\s*["']([^"']+)["']/);
46
+ if (match && match[1]) {
47
+ return match[1].trim();
48
+ }
49
+ }
50
+ catch {
51
+ // Parse error
52
+ }
53
+ return null;
54
+ }
55
+ /**
56
+ * Extract project name from pyproject.toml (Python)
57
+ */
58
+ async function fromPyprojectToml() {
59
+ if (!(await fileExists("pyproject.toml"))) {
60
+ return null;
61
+ }
62
+ try {
63
+ const content = await readFile("pyproject.toml");
64
+ // Try [project] name first (PEP 621 standard)
65
+ const projectMatch = content.match(/\[project\][\s\S]*?name\s*=\s*["']([^"']+)["']/);
66
+ if (projectMatch && projectMatch[1]) {
67
+ return projectMatch[1].trim();
68
+ }
69
+ // Fallback to [tool.poetry] name
70
+ const poetryMatch = content.match(/\[tool\.poetry\][\s\S]*?name\s*=\s*["']([^"']+)["']/);
71
+ if (poetryMatch && poetryMatch[1]) {
72
+ return poetryMatch[1].trim();
73
+ }
74
+ }
75
+ catch {
76
+ // Parse error
77
+ }
78
+ return null;
79
+ }
80
+ /**
81
+ * Extract project name from go.mod (Go)
82
+ */
83
+ async function fromGoMod() {
84
+ if (!(await fileExists("go.mod"))) {
85
+ return null;
86
+ }
87
+ try {
88
+ const content = await readFile("go.mod");
89
+ // Look for: module github.com/org/project-name
90
+ const match = content.match(/^module\s+(.+)$/m);
91
+ if (match && match[1]) {
92
+ const modulePath = match[1].trim();
93
+ // Extract the last segment of the module path
94
+ const segments = modulePath.split("/");
95
+ const lastSegment = segments[segments.length - 1];
96
+ if (lastSegment) {
97
+ return lastSegment;
98
+ }
99
+ }
100
+ }
101
+ catch {
102
+ // Parse error
103
+ }
104
+ return null;
105
+ }
106
+ /**
107
+ * Extract project name from git remote origin URL
108
+ */
109
+ function fromGitRemote() {
110
+ try {
111
+ const result = spawnSync("git", ["remote", "get-url", "origin"], {
112
+ encoding: "utf-8",
113
+ timeout: 5000,
114
+ });
115
+ if (result.status !== 0 || !result.stdout) {
116
+ return null;
117
+ }
118
+ const url = result.stdout.trim();
119
+ // Handle SSH format: git@github.com:org/repo.git
120
+ const sshMatch = url.match(/[:/]([^/]+)\.git$/);
121
+ if (sshMatch && sshMatch[1]) {
122
+ return sshMatch[1];
123
+ }
124
+ // Handle HTTPS format: https://github.com/org/repo.git or https://github.com/org/repo
125
+ const httpsMatch = url.match(/\/([^/]+?)(?:\.git)?$/);
126
+ if (httpsMatch && httpsMatch[1]) {
127
+ return httpsMatch[1];
128
+ }
129
+ }
130
+ catch {
131
+ // Git not available or not a git repo
132
+ }
133
+ return null;
134
+ }
135
+ /**
136
+ * Get project name from directory name (fallback)
137
+ */
138
+ function fromDirectory() {
139
+ return basename(process.cwd()) || "project";
140
+ }
141
+ /**
142
+ * Detect project name from available sources
143
+ *
144
+ * Tries sources in priority order:
145
+ * 1. package.json
146
+ * 2. Cargo.toml
147
+ * 3. pyproject.toml
148
+ * 4. go.mod
149
+ * 5. git remote
150
+ * 6. directory name
151
+ *
152
+ * @returns The detected project name and its source
153
+ */
154
+ export async function detectProjectName() {
155
+ // 1. Try package.json
156
+ const fromPkg = await fromPackageJson();
157
+ if (fromPkg) {
158
+ return { name: fromPkg, source: "package.json" };
159
+ }
160
+ // 2. Try Cargo.toml
161
+ const fromCargo = await fromCargoToml();
162
+ if (fromCargo) {
163
+ return { name: fromCargo, source: "Cargo.toml" };
164
+ }
165
+ // 3. Try pyproject.toml
166
+ const fromPyproject = await fromPyprojectToml();
167
+ if (fromPyproject) {
168
+ return { name: fromPyproject, source: "pyproject.toml" };
169
+ }
170
+ // 4. Try go.mod
171
+ const fromGo = await fromGoMod();
172
+ if (fromGo) {
173
+ return { name: fromGo, source: "go.mod" };
174
+ }
175
+ // 5. Try git remote
176
+ const fromRemote = fromGitRemote();
177
+ if (fromRemote) {
178
+ return { name: fromRemote, source: "git-remote" };
179
+ }
180
+ // 6. Fallback to directory name
181
+ return { name: fromDirectory(), source: "directory" };
182
+ }
183
+ /**
184
+ * Detect project name, returning just the name string
185
+ *
186
+ * Convenience function for cases where only the name is needed.
187
+ */
188
+ export async function getProjectName() {
189
+ const result = await detectProjectName();
190
+ return result.name;
191
+ }
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Semgrep integration for static analysis
3
+ *
4
+ * Provides stack-aware ruleset mapping and execution utilities
5
+ * for integrating Semgrep into the /qa workflow.
6
+ */
7
+ /**
8
+ * Semgrep finding severity levels
9
+ */
10
+ export type SemgrepSeverity = "error" | "warning" | "info";
11
+ /**
12
+ * A single Semgrep finding
13
+ */
14
+ export interface SemgrepFinding {
15
+ path: string;
16
+ line: number;
17
+ column?: number;
18
+ endLine?: number;
19
+ endColumn?: number;
20
+ message: string;
21
+ ruleId: string;
22
+ severity: SemgrepSeverity;
23
+ category?: string;
24
+ }
25
+ /**
26
+ * Result of a Semgrep scan
27
+ */
28
+ export interface SemgrepResult {
29
+ success: boolean;
30
+ findings: SemgrepFinding[];
31
+ criticalCount: number;
32
+ warningCount: number;
33
+ infoCount: number;
34
+ error?: string;
35
+ skipped?: boolean;
36
+ skipReason?: string;
37
+ }
38
+ /**
39
+ * Semgrep ruleset configuration
40
+ */
41
+ export interface SemgrepRuleset {
42
+ name: string;
43
+ description: string;
44
+ rules: string[];
45
+ }
46
+ /**
47
+ * Stack-to-ruleset mapping
48
+ *
49
+ * Maps detected stacks to appropriate Semgrep rulesets.
50
+ * Uses Semgrep's public rule registry identifiers.
51
+ */
52
+ export declare const STACK_RULESETS: Record<string, SemgrepRuleset>;
53
+ /**
54
+ * Get the appropriate Semgrep ruleset for a detected stack
55
+ *
56
+ * @param stack - The detected stack name (e.g., "nextjs", "python")
57
+ * @returns The ruleset configuration for the stack
58
+ */
59
+ export declare function getRulesForStack(stack: string | null): SemgrepRuleset;
60
+ /**
61
+ * Path to custom rules file
62
+ */
63
+ export declare const CUSTOM_RULES_PATH = ".sequant/semgrep-rules.yaml";
64
+ /**
65
+ * Check if custom rules file exists
66
+ *
67
+ * @param projectRoot - Root directory of the project
68
+ * @returns true if custom rules file exists
69
+ */
70
+ export declare function hasCustomRules(projectRoot?: string): boolean;
71
+ /**
72
+ * Get path to custom rules file if it exists
73
+ *
74
+ * @param projectRoot - Root directory of the project
75
+ * @returns Path to custom rules file, or null if it doesn't exist
76
+ */
77
+ export declare function getCustomRulesPath(projectRoot?: string): string | null;
78
+ /**
79
+ * Check if Semgrep is available
80
+ *
81
+ * Checks for both 'semgrep' command and 'npx semgrep' fallback
82
+ *
83
+ * @returns Object with availability info and command to use
84
+ */
85
+ export declare function checkSemgrepAvailability(): Promise<{
86
+ available: boolean;
87
+ command: string;
88
+ useNpx: boolean;
89
+ }>;
90
+ /**
91
+ * Parse Semgrep JSON output into findings
92
+ *
93
+ * @param output - Raw JSON output from Semgrep
94
+ * @returns Array of parsed findings
95
+ */
96
+ export declare function parseSemgrepOutput(output: string): SemgrepFinding[];
97
+ /**
98
+ * Count findings by severity
99
+ *
100
+ * @param findings - Array of Semgrep findings
101
+ * @returns Object with counts by severity
102
+ */
103
+ export declare function countFindingsBySeverity(findings: SemgrepFinding[]): {
104
+ critical: number;
105
+ warning: number;
106
+ info: number;
107
+ };
108
+ /**
109
+ * Run Semgrep scan on specified files or directories
110
+ *
111
+ * @param options - Scan options
112
+ * @returns Scan result with findings
113
+ */
114
+ export declare function runSemgrepScan(options: {
115
+ targets: string[];
116
+ stack?: string | null;
117
+ projectRoot?: string;
118
+ useCustomRules?: boolean;
119
+ }): Promise<SemgrepResult>;
120
+ /**
121
+ * Format findings for display in QA output
122
+ *
123
+ * @param findings - Array of findings to format
124
+ * @returns Formatted markdown string
125
+ */
126
+ export declare function formatFindingsForDisplay(findings: SemgrepFinding[]): string;
127
+ /**
128
+ * Generate QA verdict contribution from Semgrep results
129
+ *
130
+ * @param result - Semgrep scan result
131
+ * @returns Verdict contribution (blocking if critical findings)
132
+ */
133
+ export declare function getSemgrepVerdictContribution(result: SemgrepResult): {
134
+ blocking: boolean;
135
+ reason: string;
136
+ };