@chief-clancy/brief 0.1.2 → 0.3.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.
@@ -11,7 +11,7 @@ import { describe, expect, it } from 'vitest';
11
11
 
12
12
  const WORKFLOWS_DIR = fileURLToPath(new URL('.', import.meta.url));
13
13
 
14
- const EXPECTED_WORKFLOWS = ['board-setup.md', 'brief.md'];
14
+ const EXPECTED_WORKFLOWS = ['approve-brief.md', 'board-setup.md', 'brief.md'];
15
15
 
16
16
  describe('workflows directory structure', () => {
17
17
  it('contains exactly the expected workflow files', () => {
@@ -43,6 +43,214 @@ describe('workflows directory structure', () => {
43
43
  expect(content).toContain('## Step 1');
44
44
  expect(content).toContain('.clancy/briefs/');
45
45
  });
46
+
47
+ it('approve-brief workflow has structural step markers', () => {
48
+ const content = readFileSync(
49
+ new URL('approve-brief.md', import.meta.url),
50
+ 'utf8',
51
+ );
52
+
53
+ expect(content).toContain('## Step 1');
54
+ expect(content).toContain('.clancy/briefs/');
55
+ });
56
+ });
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // approve-brief Step 1 install-mode preflight assertions
60
+ // ---------------------------------------------------------------------------
61
+
62
+ describe('approve-brief Step 1 install-mode preflight', () => {
63
+ const content = readFileSync(
64
+ new URL('approve-brief.md', import.meta.url),
65
+ 'utf8',
66
+ );
67
+
68
+ it('detects three installation states', () => {
69
+ expect(content).toContain('standalone mode');
70
+ expect(content).toContain('standalone+board mode');
71
+ expect(content).toContain('terminal mode');
72
+ });
73
+
74
+ it('uses the same env-var probes as approve-plan and plan workflows', () => {
75
+ // Schema-pair contract: must mirror approve-plan.md and plan.md exactly.
76
+ expect(content).toContain('.clancy/.env');
77
+ expect(content).toContain('.clancy/clancy-implement.js');
78
+ });
79
+
80
+ it('hard-stops in standalone mode with the board-setup message', () => {
81
+ expect(content).toContain('No board credentials found');
82
+ expect(content).toContain('Run /clancy:board-setup first');
83
+ });
84
+
85
+ it('does NOT use the old /clancy:init standalone hard-stop', () => {
86
+ expect(content).not.toContain('Run /clancy:init to set up Clancy first');
87
+ });
88
+
89
+ it('terminal-mode preflight gates the strategist role check', () => {
90
+ expect(content).toContain(
91
+ '### 2. Terminal-mode preflight (skip in standalone+board mode)',
92
+ );
93
+ expect(content).toContain('CLANCY_ROLES` includes `strategist`');
94
+ });
95
+
96
+ it('standalone+board preflight notes the strategist role check does not apply', () => {
97
+ expect(content).toContain(
98
+ '### 3. Standalone+board preflight (only in standalone+board mode)',
99
+ );
100
+ expect(content).toContain('strategist role check above does not apply');
101
+ });
102
+
103
+ it('captures the install mode for Step 6 to read', () => {
104
+ expect(content).toContain(
105
+ "The detected install mode is captured for Step 6's pipeline label decision",
106
+ );
107
+ });
108
+ });
109
+
110
+ // ---------------------------------------------------------------------------
111
+ // approve-brief Step 6 pipeline label selection rule
112
+ // ---------------------------------------------------------------------------
113
+
114
+ describe('approve-brief Step 6 pipeline label selection rule', () => {
115
+ const content = readFileSync(
116
+ new URL('approve-brief.md', import.meta.url),
117
+ 'utf8',
118
+ );
119
+
120
+ it('has the preamble heading at the top of Step 6', () => {
121
+ expect(content).toContain(
122
+ '### Pipeline label selection rule (applies to all six platforms below)',
123
+ );
124
+ });
125
+
126
+ it('preamble enumerates rule 1 — --skip-plan flag uses build label', () => {
127
+ expect(content).toMatch(
128
+ /1\. `--skip-plan` flag is set → use `CLANCY_LABEL_BUILD`/,
129
+ );
130
+ });
131
+
132
+ it('preamble enumerates rule 2 — standalone+board uses plan label', () => {
133
+ expect(content).toMatch(
134
+ /2\. Install mode is \*\*standalone\+board\*\*[^\n]*→ use `CLANCY_LABEL_PLAN`/,
135
+ );
136
+ });
137
+
138
+ it('preamble enumerates rule 3 — terminal + planner enabled uses plan label', () => {
139
+ expect(content).toMatch(
140
+ /3\. Install mode is \*\*terminal\*\* AND `CLANCY_ROLES` includes `planner`[^\n]*→ use `CLANCY_LABEL_PLAN`/,
141
+ );
142
+ });
143
+
144
+ it('preamble enumerates rule 4 — terminal + planner not enabled uses build label', () => {
145
+ expect(content).toMatch(
146
+ /4\. Install mode is \*\*terminal\*\* AND `CLANCY_ROLES` is set but does NOT include `planner` → use `CLANCY_LABEL_BUILD`/,
147
+ );
148
+ });
149
+
150
+ it('GitHub subsection delegates to the preamble (no inline 3-rule fallthrough)', () => {
151
+ expect(content).toContain(
152
+ 'Apply the pipeline label per the rule above to every child ticket',
153
+ );
154
+ // The old per-platform fallthrough must be gone — no leftover
155
+ // "Planner role enabled" / "Planner role NOT enabled" lines anywhere.
156
+ expect(content).not.toContain('Planner role enabled');
157
+ expect(content).not.toContain('Planner role NOT enabled');
158
+ });
159
+
160
+ it('all five non-GitHub platform subsections delegate to the preamble', () => {
161
+ // Each non-GitHub platform must reference the preamble explicitly
162
+ // and must NOT use the old "same logic as GitHub/other boards" wording.
163
+ expect(content).not.toContain('same logic as GitHub');
164
+ expect(content).not.toContain('same logic as other boards');
165
+
166
+ // GitHub uses "above"; the other 5 use "at the top of Step 6". Of those
167
+ // 5, four are "pipeline label per the rule" (Jira, Linear, Shortcut,
168
+ // Notion) and one is "pipeline tag determined by the rule" (Azure
169
+ // DevOps, which uses System.Tags rather than labels).
170
+ const labelDelegations = content.match(
171
+ /Apply the pipeline label per the rule at the top of Step 6/g,
172
+ );
173
+ const tagDelegations = content.match(
174
+ /Apply the pipeline tag determined by the rule at the top of Step 6/g,
175
+ );
176
+
177
+ expect(labelDelegations).not.toBeNull();
178
+ expect(labelDelegations?.length).toBe(4);
179
+ expect(tagDelegations).not.toBeNull();
180
+ expect(tagDelegations?.length).toBe(1);
181
+ });
182
+ });
183
+
184
+ // ---------------------------------------------------------------------------
185
+ // approve-brief test permissiveness audit
186
+ // ---------------------------------------------------------------------------
187
+
188
+ describe('approve-brief test regex permissiveness audit', () => {
189
+ // These tests guard against the `\\?d` class of trap from
190
+ // feedback_workflow_md_gotchas.md by walking through the simplest wrong
191
+ // inputs that the regexes above SHOULD reject.
192
+ const content = readFileSync(
193
+ new URL('approve-brief.md', import.meta.url),
194
+ 'utf8',
195
+ );
196
+
197
+ /**
198
+ * Slice the workflow content between two literal markers, asserting both
199
+ * markers are present AND the end marker comes after the start marker.
200
+ * Without these guards a missing marker would return -1 from indexOf,
201
+ * which slice() interprets as a negative index — that produces a
202
+ * substring counted from the END of the file and silently passes
203
+ * (or silently fails) the body assertions for the wrong reason. Per
204
+ * Copilot finding on PR #222 + the test permissiveness audit discipline.
205
+ */
206
+ const sliceBetween = (start: string, end: string): string => {
207
+ const startIdx = content.indexOf(start);
208
+ const endIdx = content.indexOf(end);
209
+
210
+ expect(startIdx, `start marker not found: ${start}`).toBeGreaterThanOrEqual(
211
+ 0,
212
+ );
213
+ expect(endIdx, `end marker not found: ${end}`).toBeGreaterThanOrEqual(0);
214
+ expect(endIdx, `end marker before start marker`).toBeGreaterThan(startIdx);
215
+
216
+ return content.slice(startIdx, endIdx);
217
+ };
218
+
219
+ it('rule-2 body does NOT contain a swapped label', () => {
220
+ // Sanity check: rule 2 must reference CLANCY_LABEL_PLAN, not _BUILD.
221
+ // If someone accidentally swapped the labels in the preamble, the
222
+ // existing rule-2 assertion above would still match the heading; this
223
+ // assertion makes the swap fail loudly.
224
+ const rule2ToRule3 = sliceBetween(
225
+ '2. Install mode is **standalone+board**',
226
+ '3. Install mode is **terminal** AND `CLANCY_ROLES` includes `planner`',
227
+ );
228
+
229
+ expect(rule2ToRule3).not.toContain('CLANCY_LABEL_BUILD');
230
+ });
231
+
232
+ it('rule-3 body does NOT contain a swapped label', () => {
233
+ // Symmetric to rule-2 / rule-4. Rule 3 must reference CLANCY_LABEL_PLAN.
234
+ const rule3ToRule4 = sliceBetween(
235
+ '3. Install mode is **terminal** AND `CLANCY_ROLES` includes `planner`',
236
+ '4. Install mode is **terminal** AND `CLANCY_ROLES` is set but does NOT include `planner`',
237
+ );
238
+
239
+ expect(rule3ToRule4).not.toContain('CLANCY_LABEL_BUILD');
240
+ });
241
+
242
+ it('rule-4 body does NOT contain a swapped label', () => {
243
+ // Symmetric to the rule-2 check: rule 4 must reference
244
+ // CLANCY_LABEL_BUILD, not _PLAN. Slice from rule-4 to the rule-block
245
+ // terminator paragraph (the "This rule replaces..." line that follows
246
+ // rule 4 in the preamble).
247
+ const rule4ToTerminator = sliceBetween(
248
+ '4. Install mode is **terminal** AND `CLANCY_ROLES` is set but does NOT include `planner`',
249
+ 'This rule replaces the per-platform fallthrough',
250
+ );
251
+
252
+ expect(rule4ToTerminator).not.toContain('CLANCY_LABEL_PLAN');
253
+ });
46
254
  });
47
255
 
48
256
  // ---------------------------------------------------------------------------