sequant 1.15.2 → 1.15.3

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 (43) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/dist/bin/cli.js +3 -0
  3. package/dist/src/commands/logs.js +15 -0
  4. package/dist/src/commands/run.d.ts +114 -1
  5. package/dist/src/commands/run.js +447 -23
  6. package/dist/src/commands/stats.js +48 -0
  7. package/dist/src/lib/scope/index.d.ts +1 -0
  8. package/dist/src/lib/scope/index.js +2 -0
  9. package/dist/src/lib/scope/settings-converter.d.ts +28 -0
  10. package/dist/src/lib/scope/settings-converter.js +53 -0
  11. package/dist/src/lib/settings.d.ts +45 -0
  12. package/dist/src/lib/settings.js +30 -0
  13. package/dist/src/lib/test-tautology-detector.d.ts +122 -0
  14. package/dist/src/lib/test-tautology-detector.js +488 -0
  15. package/dist/src/lib/workflow/git-diff-utils.d.ts +39 -0
  16. package/dist/src/lib/workflow/git-diff-utils.js +142 -0
  17. package/dist/src/lib/workflow/log-writer.d.ts +9 -2
  18. package/dist/src/lib/workflow/log-writer.js +9 -3
  19. package/dist/src/lib/workflow/metrics-schema.d.ts +9 -0
  20. package/dist/src/lib/workflow/metrics-schema.js +10 -1
  21. package/dist/src/lib/workflow/phase-detection.d.ts +3 -0
  22. package/dist/src/lib/workflow/phase-detection.js +27 -1
  23. package/dist/src/lib/workflow/qa-cache.d.ts +3 -1
  24. package/dist/src/lib/workflow/qa-cache.js +2 -0
  25. package/dist/src/lib/workflow/run-log-schema.d.ts +86 -3
  26. package/dist/src/lib/workflow/run-log-schema.js +40 -2
  27. package/dist/src/lib/workflow/state-utils.d.ts +46 -0
  28. package/dist/src/lib/workflow/state-utils.js +167 -0
  29. package/dist/src/lib/workflow/token-utils.d.ts +92 -0
  30. package/dist/src/lib/workflow/token-utils.js +170 -0
  31. package/dist/src/lib/workflow/types.d.ts +6 -0
  32. package/dist/src/lib/workflow/types.js +1 -0
  33. package/package.json +1 -1
  34. package/templates/hooks/pre-tool.sh +4 -0
  35. package/templates/skills/assess/SKILL.md +1 -1
  36. package/templates/skills/exec/SKILL.md +5 -4
  37. package/templates/skills/improve/SKILL.md +37 -24
  38. package/templates/skills/loop/SKILL.md +3 -3
  39. package/templates/skills/qa/references/code-review-checklist.md +10 -11
  40. package/templates/skills/qa/scripts/quality-checks.sh +16 -0
  41. package/templates/skills/security-review/references/security-checklists.md +89 -36
  42. package/templates/skills/solve/SKILL.md +3 -1
  43. package/templates/skills/spec/SKILL.md +8 -4
@@ -24,6 +24,8 @@ export interface LogWriterOptions {
24
24
  verbose?: boolean;
25
25
  /** Log rotation settings */
26
26
  rotation?: RotationSettings;
27
+ /** Git commit SHA at run start (AC-2) */
28
+ startCommit?: string;
27
29
  }
28
30
  /**
29
31
  * Manages writing structured run logs to disk
@@ -35,6 +37,7 @@ export declare class LogWriter {
35
37
  private writeToUserLogs;
36
38
  private verbose;
37
39
  private rotation;
40
+ private startCommit?;
38
41
  constructor(options?: LogWriterOptions);
39
42
  /**
40
43
  * Initialize a new run log
@@ -65,9 +68,13 @@ export declare class LogWriter {
65
68
  *
66
69
  * Automatically rotates old logs if thresholds are exceeded.
67
70
  *
71
+ * @param options - Optional finalization options
72
+ * @param options.endCommit - Git commit SHA at run end (AC-2)
68
73
  * @returns Path to the written log file
69
74
  */
70
- finalize(): Promise<string>;
75
+ finalize(options?: {
76
+ endCommit?: string;
77
+ }): Promise<string>;
71
78
  /**
72
79
  * Get the current run log (for inspection)
73
80
  */
@@ -85,4 +92,4 @@ export declare class LogWriter {
85
92
  *
86
93
  * Utility function for creating phase logs when you have start/end times.
87
94
  */
88
- export declare function createPhaseLogFromTiming(phase: Phase, issueNumber: number, startTime: Date, endTime: Date, status: PhaseLog["status"], options?: Partial<Pick<PhaseLog, "error" | "iterations" | "filesModified" | "testsRun" | "testsPassed" | "verdict">>): PhaseLog;
95
+ export declare function createPhaseLogFromTiming(phase: Phase, issueNumber: number, startTime: Date, endTime: Date, status: PhaseLog["status"], options?: Partial<Pick<PhaseLog, "error" | "iterations" | "filesModified" | "testsRun" | "testsPassed" | "verdict" | "commitHash" | "fileDiffStats" | "cacheMetrics">>): PhaseLog;
@@ -28,11 +28,13 @@ export class LogWriter {
28
28
  writeToUserLogs;
29
29
  verbose;
30
30
  rotation;
31
+ startCommit;
31
32
  constructor(options = {}) {
32
33
  this.logPath = options.logPath ?? LOG_PATHS.project;
33
34
  this.writeToUserLogs = options.writeToUserLogs ?? false;
34
35
  this.verbose = options.verbose ?? false;
35
36
  this.rotation = options.rotation ?? DEFAULT_ROTATION_SETTINGS;
37
+ this.startCommit = options.startCommit;
36
38
  }
37
39
  /**
38
40
  * Initialize a new run log
@@ -40,7 +42,7 @@ export class LogWriter {
40
42
  * @param config - Run configuration
41
43
  */
42
44
  async initialize(config) {
43
- this.runLog = createEmptyRunLog(config);
45
+ this.runLog = createEmptyRunLog(config, { startCommit: this.startCommit });
44
46
  // Ensure log directory exists
45
47
  await this.ensureLogDirectory(this.logPath);
46
48
  if (this.writeToUserLogs) {
@@ -124,9 +126,11 @@ export class LogWriter {
124
126
  *
125
127
  * Automatically rotates old logs if thresholds are exceeded.
126
128
  *
129
+ * @param options - Optional finalization options
130
+ * @param options.endCommit - Git commit SHA at run end (AC-2)
127
131
  * @returns Path to the written log file
128
132
  */
129
- async finalize() {
133
+ async finalize(options) {
130
134
  if (!this.runLog) {
131
135
  throw new Error("LogWriter not initialized.");
132
136
  }
@@ -134,7 +138,9 @@ export class LogWriter {
134
138
  if (this.currentIssue) {
135
139
  this.completeIssue();
136
140
  }
137
- const finalLog = finalizeRunLog(this.runLog);
141
+ const finalLog = finalizeRunLog(this.runLog, {
142
+ endCommit: options?.endCommit,
143
+ });
138
144
  const filename = generateLogFilename(finalLog.runId, new Date(finalLog.startTime));
139
145
  // Write to project logs
140
146
  const projectPath = path.join(this.resolvePath(this.logPath), filename);
@@ -48,6 +48,9 @@ export declare const RunMetricsSchema: z.ZodObject<{
48
48
  linesAdded: z.ZodNumber;
49
49
  acceptanceCriteria: z.ZodNumber;
50
50
  qaIterations: z.ZodNumber;
51
+ inputTokens: z.ZodOptional<z.ZodNumber>;
52
+ outputTokens: z.ZodOptional<z.ZodNumber>;
53
+ cacheTokens: z.ZodOptional<z.ZodNumber>;
51
54
  }, z.core.$strip>;
52
55
  export type RunMetrics = z.infer<typeof RunMetricsSchema>;
53
56
  /**
@@ -86,6 +89,9 @@ export declare const MetricRunSchema: z.ZodObject<{
86
89
  linesAdded: z.ZodNumber;
87
90
  acceptanceCriteria: z.ZodNumber;
88
91
  qaIterations: z.ZodNumber;
92
+ inputTokens: z.ZodOptional<z.ZodNumber>;
93
+ outputTokens: z.ZodOptional<z.ZodNumber>;
94
+ cacheTokens: z.ZodOptional<z.ZodNumber>;
89
95
  }, z.core.$strip>;
90
96
  }, z.core.$strip>;
91
97
  export type MetricRun = z.infer<typeof MetricRunSchema>;
@@ -123,6 +129,9 @@ export declare const MetricsSchema: z.ZodObject<{
123
129
  linesAdded: z.ZodNumber;
124
130
  acceptanceCriteria: z.ZodNumber;
125
131
  qaIterations: z.ZodNumber;
132
+ inputTokens: z.ZodOptional<z.ZodNumber>;
133
+ outputTokens: z.ZodOptional<z.ZodNumber>;
134
+ cacheTokens: z.ZodOptional<z.ZodNumber>;
126
135
  }, z.core.$strip>;
127
136
  }, z.core.$strip>>;
128
137
  }, z.core.$strip>;
@@ -38,7 +38,7 @@ export const MetricPhaseSchema = z.enum([
38
38
  * Note: No file paths or code content - only aggregate counts
39
39
  */
40
40
  export const RunMetricsSchema = z.object({
41
- /** Estimated tokens used (if available, 0 if not) */
41
+ /** Estimated tokens used (if available, 0 if not) - total of input + output */
42
42
  tokensUsed: z.number().int().nonnegative(),
43
43
  /** Number of files changed during the run */
44
44
  filesChanged: z.number().int().nonnegative(),
@@ -48,6 +48,12 @@ export const RunMetricsSchema = z.object({
48
48
  acceptanceCriteria: z.number().int().nonnegative(),
49
49
  /** Number of QA iterations needed */
50
50
  qaIterations: z.number().int().nonnegative(),
51
+ /** Input tokens used (AC-4 token breakdown) */
52
+ inputTokens: z.number().int().nonnegative().optional(),
53
+ /** Output tokens used (AC-4 token breakdown) */
54
+ outputTokens: z.number().int().nonnegative().optional(),
55
+ /** Cache tokens (creation + read) (AC-4 token breakdown) */
56
+ cacheTokens: z.number().int().nonnegative().optional(),
51
57
  });
52
58
  /**
53
59
  * Single workflow run record
@@ -121,6 +127,9 @@ export function createMetricRun(options) {
121
127
  linesAdded: options.metrics?.linesAdded ?? 0,
122
128
  acceptanceCriteria: options.metrics?.acceptanceCriteria ?? 0,
123
129
  qaIterations: options.metrics?.qaIterations ?? 0,
130
+ inputTokens: options.metrics?.inputTokens,
131
+ outputTokens: options.metrics?.outputTokens,
132
+ cacheTokens: options.metrics?.cacheTokens,
124
133
  },
125
134
  };
126
135
  }
@@ -21,6 +21,9 @@ export declare function formatPhaseMarker(marker: PhaseMarker): string;
21
21
  /**
22
22
  * Parse all phase markers from a single comment body.
23
23
  *
24
+ * Phase markers inside fenced code blocks (```...```) or inline code (`...`)
25
+ * are ignored to prevent false positives from documentation examples.
26
+ *
24
27
  * @param commentBody - The full body text of a GitHub comment
25
28
  * @returns Array of parsed phase markers (empty if none found)
26
29
  */
@@ -14,6 +14,27 @@ import { execSync } from "child_process";
14
14
  import { PhaseMarkerSchema, WORKFLOW_PHASES, } from "./state-schema.js";
15
15
  /** Regex to extract phase marker JSON from HTML comments */
16
16
  const PHASE_MARKER_REGEX = /<!-- SEQUANT_PHASE: (\{[^}]+\}) -->/g;
17
+ /**
18
+ * Regex patterns for markdown code constructs that should be ignored.
19
+ * - Fenced code blocks: 3+ backticks or tildes (CommonMark spec)
20
+ * - Inline code: `...`
21
+ */
22
+ const FENCED_CODE_BLOCK_REGEX = /`{3,}[\s\S]*?`{3,}|~{3,}[\s\S]*?~{3,}/g;
23
+ const INLINE_CODE_REGEX = /`[^`\n]+`/g;
24
+ /**
25
+ * Strip markdown code blocks and inline code from text.
26
+ * This prevents phase markers inside code examples from being parsed.
27
+ *
28
+ * @param text - The text to strip code from
29
+ * @returns Text with code blocks and inline code removed
30
+ */
31
+ function stripMarkdownCode(text) {
32
+ // First remove fenced code blocks (multi-line)
33
+ let result = text.replace(FENCED_CODE_BLOCK_REGEX, "");
34
+ // Then remove inline code
35
+ result = result.replace(INLINE_CODE_REGEX, "");
36
+ return result;
37
+ }
17
38
  /**
18
39
  * Format a phase marker as an HTML comment string for embedding in GitHub comments.
19
40
  *
@@ -26,15 +47,20 @@ export function formatPhaseMarker(marker) {
26
47
  /**
27
48
  * Parse all phase markers from a single comment body.
28
49
  *
50
+ * Phase markers inside fenced code blocks (```...```) or inline code (`...`)
51
+ * are ignored to prevent false positives from documentation examples.
52
+ *
29
53
  * @param commentBody - The full body text of a GitHub comment
30
54
  * @returns Array of parsed phase markers (empty if none found)
31
55
  */
32
56
  export function parsePhaseMarkers(commentBody) {
33
57
  const markers = [];
58
+ // Strip code blocks before matching to avoid false positives
59
+ const strippedBody = stripMarkdownCode(commentBody);
34
60
  // Reset regex state for reuse
35
61
  PHASE_MARKER_REGEX.lastIndex = 0;
36
62
  let match;
37
- while ((match = PHASE_MARKER_REGEX.exec(commentBody)) !== null) {
63
+ while ((match = PHASE_MARKER_REGEX.exec(strippedBody)) !== null) {
38
64
  try {
39
65
  const parsed = JSON.parse(match[1]);
40
66
  const result = PhaseMarkerSchema.safeParse(parsed);
@@ -28,7 +28,7 @@ import { z } from "zod";
28
28
  /**
29
29
  * Check types that can be cached
30
30
  */
31
- export declare const CHECK_TYPES: readonly ["type-safety", "deleted-tests", "scope", "size", "security", "semgrep", "build", "tests"];
31
+ export declare const CHECK_TYPES: readonly ["type-safety", "deleted-tests", "scope", "size", "security", "semgrep", "build", "tests", "test-quality"];
32
32
  export type CheckType = (typeof CHECK_TYPES)[number];
33
33
  /**
34
34
  * Schema for a single cached check result
@@ -43,6 +43,7 @@ export declare const CachedCheckResultSchema: z.ZodObject<{
43
43
  scope: "scope";
44
44
  size: "size";
45
45
  tests: "tests";
46
+ "test-quality": "test-quality";
46
47
  }>;
47
48
  diffHash: z.ZodString;
48
49
  configHash: z.ZodString;
@@ -71,6 +72,7 @@ export declare const QACacheSchema: z.ZodObject<{
71
72
  scope: "scope";
72
73
  size: "size";
73
74
  tests: "tests";
75
+ "test-quality": "test-quality";
74
76
  }>;
75
77
  diffHash: z.ZodString;
76
78
  configHash: z.ZodString;
@@ -41,6 +41,7 @@ export const CHECK_TYPES = [
41
41
  "semgrep",
42
42
  "build",
43
43
  "tests",
44
+ "test-quality",
44
45
  ];
45
46
  /**
46
47
  * Schema for a single cached check result
@@ -106,6 +107,7 @@ const CHECK_INVALIDATION_PATTERNS = {
106
107
  semgrep: [/\.ts$/, /\.tsx$/, /\.js$/, /\.jsx$/, /semgrep.*\.ya?ml$/],
107
108
  build: [/\.ts$/, /\.tsx$/, /\.js$/, /\.jsx$/, /tsconfig.*\.json$/],
108
109
  tests: [/\.test\.[jt]sx?$/, /\.spec\.[jt]sx?$/, /jest\.config/],
110
+ "test-quality": [/\.test\.[jt]sx?$/, /\.spec\.[jt]sx?$/],
109
111
  };
110
112
  /**
111
113
  * QA Cache Manager
@@ -58,6 +58,30 @@ export declare const QaVerdictSchema: z.ZodEnum<{
58
58
  NEEDS_VERIFICATION: "NEEDS_VERIFICATION";
59
59
  }>;
60
60
  export type QaVerdict = z.infer<typeof QaVerdictSchema>;
61
+ /**
62
+ * File diff statistics for a single file (AC-3)
63
+ */
64
+ export declare const FileDiffStatSchema: z.ZodObject<{
65
+ path: z.ZodString;
66
+ additions: z.ZodNumber;
67
+ deletions: z.ZodNumber;
68
+ status: z.ZodEnum<{
69
+ modified: "modified";
70
+ added: "added";
71
+ deleted: "deleted";
72
+ renamed: "renamed";
73
+ }>;
74
+ }, z.core.$strip>;
75
+ export type FileDiffStat = z.infer<typeof FileDiffStatSchema>;
76
+ /**
77
+ * Cache metrics for QA phase (AC-7)
78
+ */
79
+ export declare const CacheMetricsSchema: z.ZodObject<{
80
+ hits: z.ZodNumber;
81
+ misses: z.ZodNumber;
82
+ skipped: z.ZodNumber;
83
+ }, z.core.$strip>;
84
+ export type CacheMetrics = z.infer<typeof CacheMetricsSchema>;
61
85
  /**
62
86
  * Log entry for a single phase execution
63
87
  */
@@ -92,6 +116,23 @@ export declare const PhaseLogSchema: z.ZodObject<{
92
116
  AC_NOT_MET: "AC_NOT_MET";
93
117
  NEEDS_VERIFICATION: "NEEDS_VERIFICATION";
94
118
  }>>;
119
+ commitHash: z.ZodOptional<z.ZodString>;
120
+ fileDiffStats: z.ZodOptional<z.ZodArray<z.ZodObject<{
121
+ path: z.ZodString;
122
+ additions: z.ZodNumber;
123
+ deletions: z.ZodNumber;
124
+ status: z.ZodEnum<{
125
+ modified: "modified";
126
+ added: "added";
127
+ deleted: "deleted";
128
+ renamed: "renamed";
129
+ }>;
130
+ }, z.core.$strip>>>;
131
+ cacheMetrics: z.ZodOptional<z.ZodObject<{
132
+ hits: z.ZodNumber;
133
+ misses: z.ZodNumber;
134
+ skipped: z.ZodNumber;
135
+ }, z.core.$strip>>;
95
136
  }, z.core.$strip>;
96
137
  export type PhaseLog = z.infer<typeof PhaseLogSchema>;
97
138
  /**
@@ -137,6 +178,23 @@ export declare const IssueLogSchema: z.ZodObject<{
137
178
  AC_NOT_MET: "AC_NOT_MET";
138
179
  NEEDS_VERIFICATION: "NEEDS_VERIFICATION";
139
180
  }>>;
181
+ commitHash: z.ZodOptional<z.ZodString>;
182
+ fileDiffStats: z.ZodOptional<z.ZodArray<z.ZodObject<{
183
+ path: z.ZodString;
184
+ additions: z.ZodNumber;
185
+ deletions: z.ZodNumber;
186
+ status: z.ZodEnum<{
187
+ modified: "modified";
188
+ added: "added";
189
+ deleted: "deleted";
190
+ renamed: "renamed";
191
+ }>;
192
+ }, z.core.$strip>>>;
193
+ cacheMetrics: z.ZodOptional<z.ZodObject<{
194
+ hits: z.ZodNumber;
195
+ misses: z.ZodNumber;
196
+ skipped: z.ZodNumber;
197
+ }, z.core.$strip>>;
140
198
  }, z.core.$strip>>;
141
199
  totalDurationSeconds: z.ZodNumber;
142
200
  }, z.core.$strip>;
@@ -237,6 +295,23 @@ export declare const RunLogSchema: z.ZodObject<{
237
295
  AC_NOT_MET: "AC_NOT_MET";
238
296
  NEEDS_VERIFICATION: "NEEDS_VERIFICATION";
239
297
  }>>;
298
+ commitHash: z.ZodOptional<z.ZodString>;
299
+ fileDiffStats: z.ZodOptional<z.ZodArray<z.ZodObject<{
300
+ path: z.ZodString;
301
+ additions: z.ZodNumber;
302
+ deletions: z.ZodNumber;
303
+ status: z.ZodEnum<{
304
+ modified: "modified";
305
+ added: "added";
306
+ deleted: "deleted";
307
+ renamed: "renamed";
308
+ }>;
309
+ }, z.core.$strip>>>;
310
+ cacheMetrics: z.ZodOptional<z.ZodObject<{
311
+ hits: z.ZodNumber;
312
+ misses: z.ZodNumber;
313
+ skipped: z.ZodNumber;
314
+ }, z.core.$strip>>;
240
315
  }, z.core.$strip>>;
241
316
  totalDurationSeconds: z.ZodNumber;
242
317
  }, z.core.$strip>>;
@@ -246,6 +321,8 @@ export declare const RunLogSchema: z.ZodObject<{
246
321
  failed: z.ZodNumber;
247
322
  totalDurationSeconds: z.ZodNumber;
248
323
  }, z.core.$strip>;
324
+ startCommit: z.ZodOptional<z.ZodString>;
325
+ endCommit: z.ZodOptional<z.ZodString>;
249
326
  }, z.core.$strip>;
250
327
  export type RunLog = z.infer<typeof RunLogSchema>;
251
328
  /**
@@ -269,9 +346,12 @@ export declare function generateLogFilename(runId: string, startTime: Date): str
269
346
  * Create an empty run log with initial values
270
347
  *
271
348
  * @param config - Run configuration
349
+ * @param options - Optional fields including startCommit
272
350
  * @returns Initial RunLog structure
273
351
  */
274
- export declare function createEmptyRunLog(config: RunConfig): Omit<RunLog, "endTime">;
352
+ export declare function createEmptyRunLog(config: RunConfig, options?: {
353
+ startCommit?: string;
354
+ }): Omit<RunLog, "endTime">;
275
355
  /**
276
356
  * Create a phase log entry
277
357
  *
@@ -288,11 +368,14 @@ export declare function createPhaseLog(phase: Phase, issueNumber: number): Omit<
288
368
  * @param options - Additional fields (error, filesModified, verdict, etc.)
289
369
  * @returns Complete PhaseLog
290
370
  */
291
- export declare function completePhaseLog(phaseLog: Omit<PhaseLog, "endTime" | "durationSeconds" | "status">, status: PhaseStatus, options?: Partial<Pick<PhaseLog, "error" | "iterations" | "filesModified" | "testsRun" | "testsPassed" | "verdict">>): PhaseLog;
371
+ export declare function completePhaseLog(phaseLog: Omit<PhaseLog, "endTime" | "durationSeconds" | "status">, status: PhaseStatus, options?: Partial<Pick<PhaseLog, "error" | "iterations" | "filesModified" | "testsRun" | "testsPassed" | "verdict" | "commitHash" | "fileDiffStats" | "cacheMetrics">>): PhaseLog;
292
372
  /**
293
373
  * Finalize a run log with summary statistics
294
374
  *
295
375
  * @param runLog - Partial run log
376
+ * @param options - Optional fields including endCommit
296
377
  * @returns Complete RunLog with endTime and summary
297
378
  */
298
- export declare function finalizeRunLog(runLog: Omit<RunLog, "endTime">): RunLog;
379
+ export declare function finalizeRunLog(runLog: Omit<RunLog, "endTime">, options?: {
380
+ endCommit?: string;
381
+ }): RunLog;
@@ -51,6 +51,30 @@ export const QaVerdictSchema = z.enum([
51
51
  "AC_NOT_MET",
52
52
  "NEEDS_VERIFICATION",
53
53
  ]);
54
+ /**
55
+ * File diff statistics for a single file (AC-3)
56
+ */
57
+ export const FileDiffStatSchema = z.object({
58
+ /** File path relative to repository root */
59
+ path: z.string(),
60
+ /** Number of lines added */
61
+ additions: z.number().int().nonnegative(),
62
+ /** Number of lines deleted */
63
+ deletions: z.number().int().nonnegative(),
64
+ /** Change status */
65
+ status: z.enum(["added", "modified", "deleted", "renamed"]),
66
+ });
67
+ /**
68
+ * Cache metrics for QA phase (AC-7)
69
+ */
70
+ export const CacheMetricsSchema = z.object({
71
+ /** Number of cache hits */
72
+ hits: z.number().int().nonnegative(),
73
+ /** Number of cache misses */
74
+ misses: z.number().int().nonnegative(),
75
+ /** Number of skipped checks */
76
+ skipped: z.number().int().nonnegative(),
77
+ });
54
78
  /**
55
79
  * Log entry for a single phase execution
56
80
  */
@@ -79,6 +103,12 @@ export const PhaseLogSchema = z.object({
79
103
  testsPassed: z.number().int().nonnegative().optional(),
80
104
  /** Parsed QA verdict (only for qa phase) */
81
105
  verdict: QaVerdictSchema.optional(),
106
+ /** Git commit SHA after phase completes (AC-2) */
107
+ commitHash: z.string().optional(),
108
+ /** Per-file diff statistics (AC-3) */
109
+ fileDiffStats: z.array(FileDiffStatSchema).optional(),
110
+ /** Cache metrics for QA phase (AC-7) */
111
+ cacheMetrics: CacheMetricsSchema.optional(),
82
112
  });
83
113
  /**
84
114
  * Complete execution record for a single issue
@@ -147,6 +177,10 @@ export const RunLogSchema = z.object({
147
177
  issues: z.array(IssueLogSchema),
148
178
  /** Summary statistics */
149
179
  summary: RunSummarySchema,
180
+ /** Git commit SHA at run start (AC-2) */
181
+ startCommit: z.string().optional(),
182
+ /** Git commit SHA at run end (AC-2) */
183
+ endCommit: z.string().optional(),
150
184
  });
151
185
  /**
152
186
  * Default log directory paths
@@ -172,9 +206,10 @@ export function generateLogFilename(runId, startTime) {
172
206
  * Create an empty run log with initial values
173
207
  *
174
208
  * @param config - Run configuration
209
+ * @param options - Optional fields including startCommit
175
210
  * @returns Initial RunLog structure
176
211
  */
177
- export function createEmptyRunLog(config) {
212
+ export function createEmptyRunLog(config, options) {
178
213
  const runId = randomUUID();
179
214
  const startTime = new Date().toISOString();
180
215
  return {
@@ -189,6 +224,7 @@ export function createEmptyRunLog(config) {
189
224
  failed: 0,
190
225
  totalDurationSeconds: 0,
191
226
  },
227
+ startCommit: options?.startCommit,
192
228
  };
193
229
  }
194
230
  /**
@@ -229,9 +265,10 @@ export function completePhaseLog(phaseLog, status, options) {
229
265
  * Finalize a run log with summary statistics
230
266
  *
231
267
  * @param runLog - Partial run log
268
+ * @param options - Optional fields including endCommit
232
269
  * @returns Complete RunLog with endTime and summary
233
270
  */
234
- export function finalizeRunLog(runLog) {
271
+ export function finalizeRunLog(runLog, options) {
235
272
  const endTime = new Date();
236
273
  const startTime = new Date(runLog.startTime);
237
274
  const totalDurationSeconds = (endTime.getTime() - startTime.getTime()) / 1000;
@@ -246,5 +283,6 @@ export function finalizeRunLog(runLog) {
246
283
  failed,
247
284
  totalDurationSeconds,
248
285
  },
286
+ endCommit: options?.endCommit ?? runLog.endCommit,
249
287
  };
250
288
  }
@@ -128,3 +128,49 @@ export interface DiscoverResult {
128
128
  * and returns information about worktrees not yet in the state file.
129
129
  */
130
130
  export declare function discoverUntrackedWorktrees(options?: DiscoverOptions): Promise<DiscoverResult>;
131
+ export interface ReconcileOptions {
132
+ /** State file path (default: .sequant/state.json) */
133
+ statePath?: string;
134
+ /** Enable verbose logging */
135
+ verbose?: boolean;
136
+ }
137
+ export interface ReconcileResult {
138
+ /** Whether reconciliation was successful */
139
+ success: boolean;
140
+ /** Issues that were advanced from ready_for_merge to merged */
141
+ advanced: number[];
142
+ /** Issues checked but still ready_for_merge */
143
+ stillPending: number[];
144
+ /** Error message if failed */
145
+ error?: string;
146
+ }
147
+ /**
148
+ * Check if a branch has been merged into main using git
149
+ *
150
+ * @param branchName - The branch name to check (e.g., "feature/33-some-title")
151
+ * @returns true if the branch is merged into main, false otherwise
152
+ */
153
+ export declare function isBranchMergedIntoMain(branchName: string): boolean;
154
+ /**
155
+ * Check if a feature branch for an issue is merged into main
156
+ *
157
+ * Tries multiple detection methods:
158
+ * 1. Check if branch exists and is merged via `git branch --merged main`
159
+ * 2. Check for merge commits mentioning the issue
160
+ *
161
+ * @param issueNumber - The issue number to check
162
+ * @returns true if the issue's work is merged into main
163
+ */
164
+ export declare function isIssueMergedIntoMain(issueNumber: number): boolean;
165
+ /**
166
+ * Lightweight state reconciliation at run start
167
+ *
168
+ * Checks issues in `ready_for_merge` state and advances them to `merged`
169
+ * if their PRs are merged or their branches are in main.
170
+ *
171
+ * This prevents re-running already completed issues.
172
+ *
173
+ * @param options - Reconciliation options
174
+ * @returns Result with lists of advanced and still-pending issues
175
+ */
176
+ export declare function reconcileStateAtStartup(options?: ReconcileOptions): Promise<ReconcileResult>;