next-issue 1.0.4 → 1.0.5

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/README.md CHANGED
@@ -325,8 +325,9 @@ A `setupCommand` that fails, or that does not finish in `setupTimeoutMinutes`,
325
325
  hands the issue to a person. The wait for the checks has its own limit, so
326
326
  `commandTimeoutMinutes` holds for every other `git` and `gh` command. State per
327
327
  issue goes to `.next-issue/<issue>.json`, so a new run continues where the last
328
- run stopped. A state file that is not valid JSON gives the issue an `error`
329
- outcome that names the file, rather than a silent skip.
328
+ run stopped. A state file that is not valid JSON, or that does not hold a valid
329
+ state, gives the issue an `error` outcome that names the file, rather than a
330
+ silent skip.
330
331
 
331
332
  ## Retry an issue that waits for a person
332
333
 
package/dist/state.mjs CHANGED
@@ -19,12 +19,48 @@ export async function readState(repo, issue) {
19
19
  }
20
20
  throw error;
21
21
  }
22
+ let parsed;
22
23
  try {
23
- return JSON.parse(text);
24
+ parsed = JSON.parse(text);
24
25
  }
25
26
  catch {
26
27
  throw new Error(`the state file ${path} is not valid JSON`);
27
28
  }
29
+ if (!isState(parsed)) {
30
+ throw new Error(`the state file ${path} does not hold a valid state`);
31
+ }
32
+ return parsed;
33
+ }
34
+ const PHASES = ["claimed", "implemented", "review"];
35
+ function isRecord(value) {
36
+ return typeof value === "object" && value !== null;
37
+ }
38
+ function isOptional(value, type) {
39
+ return value === undefined || typeof value === type;
40
+ }
41
+ function isBaseFix(value) {
42
+ return isRecord(value) && typeof value.sha === "string" && typeof value.head === "string";
43
+ }
44
+ function isReviewRound(value) {
45
+ return (isRecord(value) &&
46
+ typeof value.round === "number" &&
47
+ typeof value.fingerprint === "string" &&
48
+ typeof value.findings === "string" &&
49
+ isOptional(value.dispute, "string"));
50
+ }
51
+ function isState(value) {
52
+ return (isRecord(value) &&
53
+ typeof value.issue === "number" &&
54
+ PHASES.includes(value.phase) &&
55
+ typeof value.branch === "string" &&
56
+ isOptional(value.pr, "number") &&
57
+ isOptional(value.title, "string") &&
58
+ typeof value.ciFixes === "number" &&
59
+ isOptional(value.commitFixes, "number") &&
60
+ (value.baseFix === undefined || isBaseFix(value.baseFix)) &&
61
+ typeof value.reviewRounds === "number" &&
62
+ Array.isArray(value.reviewLog) &&
63
+ value.reviewLog.every(isReviewRound));
28
64
  }
29
65
  export async function savedIssues(repo) {
30
66
  let names;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-issue",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Autonomous issue harness driven by the Claude Agent SDK",
5
5
  "keywords": [
6
6
  "github",