tdd-enforcer 0.2.6 → 0.2.8

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.
@@ -1,46 +1,54 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { loadPhaseState, savePhaseState, loadConfig, headMessage, nextPhase, initGit, stageFiles } from "../../engine/index.js";
4
- import type { PhaseState, Config, Phase } from "../../engine/types.js";
3
+ import {
4
+ headMessage,
5
+ initGit,
6
+ loadConfig,
7
+ loadPhaseState,
8
+ nextPhase,
9
+ savePhaseState,
10
+ stageFiles,
11
+ } from "../../engine/index.js";
12
+ import type { Config, PhaseState } from "../../engine/types.js";
5
13
 
6
14
  export type TddLoadResult =
7
- | { ok: true; state: PhaseState; config: Config }
8
- | { ok: false; reason: string };
15
+ | { ok: true; state: PhaseState; config: Config }
16
+ | { ok: false; reason: string };
9
17
 
10
18
  /**
11
19
  * Recover state.json from private git HEAD, or create default.
12
20
  * Returns the state (does not save to disk — caller does that).
13
21
  */
14
22
  function recoverState(
15
- root: string,
16
- tddDir: string,
17
- deps: {
18
- existsSync: typeof existsSync;
19
- headMessage: typeof headMessage;
20
- nextPhase: typeof nextPhase;
21
- } = { existsSync, headMessage, nextPhase },
23
+ root: string,
24
+ tddDir: string,
25
+ deps: {
26
+ existsSync: typeof existsSync;
27
+ headMessage: typeof headMessage;
28
+ nextPhase: typeof nextPhase;
29
+ } = { existsSync, headMessage, nextPhase },
22
30
  ): PhaseState {
23
- const gitDir = join(tddDir, ".git");
24
- if (deps.existsSync(gitDir)) {
25
- try {
26
- const msg = deps.headMessage(root);
27
- const m = msg.match(/^tdd: (red|green|refactor|init)$/);
28
- if (m) {
29
- const label = m[1];
30
- if (label === "init") {
31
- return { enabled: false, current: "red" };
32
- }
33
- if (label !== "red" && label !== "green" && label !== "refactor") {
34
- return { enabled: false, current: "red" };
35
- }
36
- const next = deps.nextPhase(label);
37
- return { enabled: true, current: next ?? "red" };
38
- }
39
- } catch {
40
- // No commits or bad HEAD — fall through to default
41
- }
42
- }
43
- return { enabled: false, current: "red" };
31
+ const gitDir = join(tddDir, ".git");
32
+ if (deps.existsSync(gitDir)) {
33
+ try {
34
+ const msg = deps.headMessage(root);
35
+ const m = msg.match(/^tdd: (red|green|refactor|init)$/);
36
+ if (m) {
37
+ const label = m[1];
38
+ if (label === "init") {
39
+ return { enabled: false, current: "red" };
40
+ }
41
+ if (label !== "red" && label !== "green" && label !== "refactor") {
42
+ return { enabled: false, current: "red" };
43
+ }
44
+ const next = deps.nextPhase(label);
45
+ return { enabled: true, current: next ?? "red" };
46
+ }
47
+ } catch {
48
+ // No commits or bad HEAD — fall through to default
49
+ }
50
+ }
51
+ return { enabled: false, current: "red" };
44
52
  }
45
53
 
46
54
  /**
@@ -52,69 +60,87 @@ function recoverState(
52
60
  * Callers must check state.enabled themselves if they need active enforcement.
53
61
  */
54
62
  export function loadTddState(
55
- root: string,
56
- deps: {
57
- existsSync: typeof existsSync;
58
- loadConfig: typeof loadConfig;
59
- initGit: typeof initGit;
60
- loadPhaseState: typeof loadPhaseState;
61
- savePhaseState: typeof savePhaseState;
62
- headMessage: typeof headMessage;
63
- nextPhase: typeof nextPhase;
64
- stageFiles: typeof stageFiles;
65
- } = {
66
- existsSync,
67
- loadConfig,
68
- initGit,
69
- loadPhaseState,
70
- savePhaseState,
71
- headMessage,
72
- nextPhase,
73
- stageFiles,
74
- },
63
+ root: string,
64
+ deps: {
65
+ existsSync: typeof existsSync;
66
+ loadConfig: typeof loadConfig;
67
+ initGit: typeof initGit;
68
+ loadPhaseState: typeof loadPhaseState;
69
+ savePhaseState: typeof savePhaseState;
70
+ headMessage: typeof headMessage;
71
+ nextPhase: typeof nextPhase;
72
+ stageFiles: typeof stageFiles;
73
+ } = {
74
+ existsSync,
75
+ loadConfig,
76
+ initGit,
77
+ loadPhaseState,
78
+ savePhaseState,
79
+ headMessage,
80
+ nextPhase,
81
+ stageFiles,
82
+ },
75
83
  ): TddLoadResult {
76
- const tddDir = join(root, ".pi", "tdd");
77
- if (!deps.existsSync(tddDir)) {
78
- return { ok: false, reason: "Missing .pi/tdd/ directory. See the tdd-enforcer skill to learn how to set up TDD configs." };
79
- }
84
+ const tddDir = join(root, ".pi", "tdd");
85
+ if (!deps.existsSync(tddDir)) {
86
+ return {
87
+ ok: false,
88
+ reason:
89
+ "Missing .pi/tdd/ directory. See the tdd-enforcer skill to learn how to set up TDD configs.",
90
+ };
91
+ }
80
92
 
81
- const rulesPath = join(tddDir, "rules.json");
82
- if (!deps.existsSync(rulesPath)) {
83
- return { ok: false, reason: "Missing .pi/tdd/rules.json. See the tdd-enforcer skill to learn how to set up TDD configs." };
84
- }
93
+ const rulesPath = join(tddDir, "rules.json");
94
+ if (!deps.existsSync(rulesPath)) {
95
+ return {
96
+ ok: false,
97
+ reason:
98
+ "Missing .pi/tdd/rules.json. See the tdd-enforcer skill to learn how to set up TDD configs.",
99
+ };
100
+ }
85
101
 
86
- let config: Config;
87
- try {
88
- config = deps.loadConfig(root);
89
- } catch (e) {
90
- return { ok: false, reason: `Invalid .pi/tdd/rules.json: ${(e as Error).message}. See the tdd-enforcer skill.` };
91
- }
102
+ let config: Config;
103
+ try {
104
+ config = deps.loadConfig(root);
105
+ } catch (e) {
106
+ return {
107
+ ok: false,
108
+ reason: `Invalid .pi/tdd/rules.json: ${(e as Error).message}. See the tdd-enforcer skill.`,
109
+ };
110
+ }
92
111
 
93
- // Init git if missing — required for state recovery and all consumers
94
- const gitDir = join(tddDir, ".git");
95
- if (!deps.existsSync(gitDir)) {
96
- try {
97
- deps.initGit(root);
98
- } catch (e) {
99
- return { ok: false, reason: `Failed to initialise private git repo: ${(e as Error).message}` };
100
- }
101
- }
112
+ // Init git if missing — required for state recovery and all consumers
113
+ const gitDir = join(tddDir, ".git");
114
+ if (!deps.existsSync(gitDir)) {
115
+ try {
116
+ deps.initGit(root);
117
+ } catch (e) {
118
+ return {
119
+ ok: false,
120
+ reason: `Failed to initialise private git repo: ${(e as Error).message}`,
121
+ };
122
+ }
123
+ }
102
124
 
103
- // Auto-create state.json if missing or corrupted
104
- const phasePath = join(tddDir, "state.json");
105
- let state: PhaseState | undefined;
106
- if (deps.existsSync(phasePath)) {
107
- try {
108
- state = deps.loadPhaseState(root);
109
- } catch {
110
- // Corrupted — recover below
111
- }
112
- }
113
- if (!state) {
114
- state = recoverState(root, tddDir, { existsSync: deps.existsSync, headMessage: deps.headMessage, nextPhase: deps.nextPhase });
115
- deps.savePhaseState(root, state);
116
- deps.stageFiles(root, [".pi/tdd/state.json"]);
117
- }
125
+ // Auto-create state.json if missing or corrupted
126
+ const phasePath = join(tddDir, "state.json");
127
+ let state: PhaseState | undefined;
128
+ if (deps.existsSync(phasePath)) {
129
+ try {
130
+ state = deps.loadPhaseState(root);
131
+ } catch {
132
+ // Corrupted — recover below
133
+ }
134
+ }
135
+ if (!state) {
136
+ state = recoverState(root, tddDir, {
137
+ existsSync: deps.existsSync,
138
+ headMessage: deps.headMessage,
139
+ nextPhase: deps.nextPhase,
140
+ });
141
+ deps.savePhaseState(root, state);
142
+ deps.stageFiles(root, [".pi/tdd/state.json"]);
143
+ }
118
144
 
119
- return { ok: true, state, config };
145
+ return { ok: true, state, config };
120
146
  }