issue-flow 0.9.0 → 0.11.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.
- package/dist/{analyze-VSWAEVN3.js → analyze-7B5TORNK.js} +13 -8
- package/dist/analyze-7B5TORNK.js.map +1 -0
- package/dist/apply-WUJHDD7A.js +788 -0
- package/dist/apply-WUJHDD7A.js.map +1 -0
- package/dist/{chunk-NB6WKTBL.js → chunk-3BOHTH4V.js} +2 -2
- package/dist/chunk-3D4A4B46.js +2 -0
- package/dist/chunk-47VIN33V.js +983 -0
- package/dist/chunk-47VIN33V.js.map +1 -0
- package/dist/{chunk-DP43WJY4.js → chunk-6N7LZZ5U.js} +19 -13
- package/dist/chunk-6N7LZZ5U.js.map +1 -0
- package/dist/{chunk-KXAG24DD.js → chunk-AAY6KM7N.js} +6 -3
- package/dist/chunk-AAY6KM7N.js.map +1 -0
- package/dist/{chunk-VNKSMWDN.js → chunk-GANSDJV7.js} +4 -4
- package/dist/{chunk-57MH6RYJ.js → chunk-HC2MT7BX.js} +249 -181
- package/dist/chunk-HC2MT7BX.js.map +1 -0
- package/dist/chunk-HQYXVIH6.js +110 -0
- package/dist/chunk-HQYXVIH6.js.map +1 -0
- package/dist/{chunk-66ZGYXTH.js → chunk-JW7WLSZ6.js} +15 -11
- package/dist/chunk-JW7WLSZ6.js.map +1 -0
- package/dist/{chunk-M37YZYDO.js → chunk-LVKQ2AKT.js} +3 -3
- package/dist/{chunk-TLSIK656.js → chunk-MKD2D7WE.js} +12 -8
- package/dist/chunk-MKD2D7WE.js.map +1 -0
- package/dist/{chunk-WHEU55ZP.js → chunk-MSPUB635.js} +15 -11
- package/dist/chunk-MSPUB635.js.map +1 -0
- package/dist/chunk-OBLVLIUS.js +285 -0
- package/dist/chunk-OBLVLIUS.js.map +1 -0
- package/dist/{chunk-JGKPSWOA.js → chunk-PL6CNMVS.js} +2 -2
- package/dist/chunk-RKTEV6EG.js +246 -0
- package/dist/chunk-RKTEV6EG.js.map +1 -0
- package/dist/{chunk-TJMZWRS6.js → chunk-UBIQXCVE.js} +15 -11
- package/dist/chunk-UBIQXCVE.js.map +1 -0
- package/dist/{chunk-X7XBF5TS.js → chunk-VCKX262Z.js} +12 -8
- package/dist/chunk-VCKX262Z.js.map +1 -0
- package/dist/{chunk-MTJQOOMG.js → chunk-VQ5VN7DF.js} +123 -4
- package/dist/chunk-VQ5VN7DF.js.map +1 -0
- package/dist/cli.js +32 -21
- package/dist/cli.js.map +1 -1
- package/dist/{config-J5DW6HSY.js → config-4THHOXNN.js} +7 -3
- package/dist/execute-5OEKLM5X.js +15 -0
- package/dist/{generate-ERDPOTKX.js → generate-6RA2MFJB.js} +99 -12
- package/dist/generate-6RA2MFJB.js.map +1 -0
- package/dist/init-PRMOTQ7N.js +9 -0
- package/dist/plan-KENR6MJ3.js +17 -0
- package/dist/policy-XQW5NUIE.js +117 -0
- package/dist/policy-XQW5NUIE.js.map +1 -0
- package/dist/pr-KDGKG4BP.js +21 -0
- package/dist/pr-review-CAMAB7KO.js +17 -0
- package/dist/prd-V3MWDIHK.js +17 -0
- package/dist/review-7LN47SAN.js +15 -0
- package/dist/review-7LN47SAN.js.map +1 -0
- package/dist/{run-BRBFHVUO.js → run-H75MD5BQ.js} +26 -25
- package/dist/{run-BRBFHVUO.js.map → run-H75MD5BQ.js.map} +1 -1
- package/dist/{web-WQISLO66.js → web-ANQQTTRD.js} +5 -5
- package/package.json +1 -1
- package/prompts/analyze.md +24 -0
- package/prompts/execute.md +29 -1
- package/prompts/generate.md +61 -7
- package/prompts/plan.md +24 -1
- package/prompts/pr-review.md +65 -4
- package/prompts/pr.md +37 -3
- package/prompts/prd.md +17 -0
- package/prompts/review.md +40 -0
- package/dist/analyze-VSWAEVN3.js.map +0 -1
- package/dist/chunk-57MH6RYJ.js.map +0 -1
- package/dist/chunk-66ZGYXTH.js.map +0 -1
- package/dist/chunk-DP43WJY4.js.map +0 -1
- package/dist/chunk-DTNM3LTO.js +0 -101
- package/dist/chunk-DTNM3LTO.js.map +0 -1
- package/dist/chunk-KXAG24DD.js.map +0 -1
- package/dist/chunk-M72ZEPWD.js +0 -135
- package/dist/chunk-M72ZEPWD.js.map +0 -1
- package/dist/chunk-MTJQOOMG.js.map +0 -1
- package/dist/chunk-SLHZPB3C.js +0 -50
- package/dist/chunk-SLHZPB3C.js.map +0 -1
- package/dist/chunk-TJMZWRS6.js.map +0 -1
- package/dist/chunk-TLSIK656.js.map +0 -1
- package/dist/chunk-WHEU55ZP.js.map +0 -1
- package/dist/chunk-X7XBF5TS.js.map +0 -1
- package/dist/execute-Z2DKGVLR.js +0 -14
- package/dist/generate-ERDPOTKX.js.map +0 -1
- package/dist/init-UY6ZCIFT.js +0 -9
- package/dist/plan-REMAMCX4.js +0 -16
- package/dist/pr-KERP52TI.js +0 -20
- package/dist/pr-review-IU4N3TEK.js +0 -16
- package/dist/prd-SHVQW6RM.js +0 -16
- package/dist/review-L5F77QAC.js +0 -14
- /package/dist/{chunk-NB6WKTBL.js.map → chunk-3BOHTH4V.js.map} +0 -0
- /package/dist/{config-J5DW6HSY.js.map → chunk-3D4A4B46.js.map} +0 -0
- /package/dist/{chunk-VNKSMWDN.js.map → chunk-GANSDJV7.js.map} +0 -0
- /package/dist/{chunk-M37YZYDO.js.map → chunk-LVKQ2AKT.js.map} +0 -0
- /package/dist/{chunk-JGKPSWOA.js.map → chunk-PL6CNMVS.js.map} +0 -0
- /package/dist/{execute-Z2DKGVLR.js.map → config-4THHOXNN.js.map} +0 -0
- /package/dist/{init-UY6ZCIFT.js.map → execute-5OEKLM5X.js.map} +0 -0
- /package/dist/{plan-REMAMCX4.js.map → init-PRMOTQ7N.js.map} +0 -0
- /package/dist/{pr-KERP52TI.js.map → plan-KENR6MJ3.js.map} +0 -0
- /package/dist/{pr-review-IU4N3TEK.js.map → pr-KDGKG4BP.js.map} +0 -0
- /package/dist/{prd-SHVQW6RM.js.map → pr-review-CAMAB7KO.js.map} +0 -0
- /package/dist/{review-L5F77QAC.js.map → prd-V3MWDIHK.js.map} +0 -0
- /package/dist/{web-WQISLO66.js.map → web-ANQQTTRD.js.map} +0 -0
- /package/web/{CLAUDE.md → AGENTS.md} +0 -0
|
@@ -0,0 +1,788 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "./chunk-3D4A4B46.js";
|
|
3
|
+
import {
|
|
4
|
+
loadRepositoryPolicy
|
|
5
|
+
} from "./chunk-47VIN33V.js";
|
|
6
|
+
import {
|
|
7
|
+
getProjectRoot
|
|
8
|
+
} from "./chunk-VQ5VN7DF.js";
|
|
9
|
+
import "./chunk-HC2MT7BX.js";
|
|
10
|
+
|
|
11
|
+
// src/scaffold/apply.ts
|
|
12
|
+
import { access, mkdir, writeFile } from "fs/promises";
|
|
13
|
+
import { basename, dirname, join as join2 } from "path";
|
|
14
|
+
|
|
15
|
+
// src/scaffold/plan.ts
|
|
16
|
+
import { readFile } from "fs/promises";
|
|
17
|
+
import { join } from "path";
|
|
18
|
+
|
|
19
|
+
// src/conventions/defaults.ts
|
|
20
|
+
var DEFAULT_ISSUE_TYPES = [
|
|
21
|
+
{
|
|
22
|
+
name: "Idea",
|
|
23
|
+
slug: "idea",
|
|
24
|
+
order: 1,
|
|
25
|
+
summary: "A hypothesis, opportunity or perceived problem, not yet analysed.",
|
|
26
|
+
executable: false,
|
|
27
|
+
native: false
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: "Research",
|
|
31
|
+
slug: "research",
|
|
32
|
+
order: 2,
|
|
33
|
+
summary: "An investigation that produces knowledge: analysis, benchmark, feasibility.",
|
|
34
|
+
executable: false,
|
|
35
|
+
native: false
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "Epic",
|
|
39
|
+
slug: "epic",
|
|
40
|
+
order: 3,
|
|
41
|
+
summary: "An umbrella objective delivered through sub-issues.",
|
|
42
|
+
executable: false,
|
|
43
|
+
native: false
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: "Feature",
|
|
47
|
+
slug: "feature",
|
|
48
|
+
order: 4,
|
|
49
|
+
summary: "A new capability or a change to what the product does.",
|
|
50
|
+
executable: true,
|
|
51
|
+
native: true
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "Bug",
|
|
55
|
+
slug: "bug",
|
|
56
|
+
order: 5,
|
|
57
|
+
summary: "Existing behaviour that diverges from what is expected.",
|
|
58
|
+
executable: true,
|
|
59
|
+
native: true
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: "Task",
|
|
63
|
+
slug: "task",
|
|
64
|
+
order: 6,
|
|
65
|
+
summary: "Concrete work that is neither a feature nor a bug.",
|
|
66
|
+
executable: true,
|
|
67
|
+
native: true
|
|
68
|
+
}
|
|
69
|
+
];
|
|
70
|
+
var NON_TYPES = [
|
|
71
|
+
{
|
|
72
|
+
concept: "Documentation",
|
|
73
|
+
instead: "`Task` + label `docs`",
|
|
74
|
+
why: "The work is a task; what varies is the area it touches."
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
concept: "Maintenance, chore",
|
|
78
|
+
instead: "`Task`",
|
|
79
|
+
why: "That is already what `Task` means."
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
concept: "Refactor, technical debt",
|
|
83
|
+
instead: "`Task` + label `tech-debt`",
|
|
84
|
+
why: "A cross-cutting characteristic, not a distinct nature of work."
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
concept: "Security",
|
|
88
|
+
instead: "the real type (`Bug`/`Task`/`Feature`) + label `security`",
|
|
89
|
+
why: "It cuts across every type; as a type it would hide whether the item is a flaw or preventive work."
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
concept: "Spike, investigation",
|
|
93
|
+
instead: "`Research`",
|
|
94
|
+
why: "The same concept under a different name."
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
concept: "Enhancement",
|
|
98
|
+
instead: "`Feature`",
|
|
99
|
+
why: "A change to what the product does is a feature, whether it is new or an improvement."
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
concept: "Proposal, RFC",
|
|
103
|
+
instead: "`Research`, and an ADR for the decision it produces",
|
|
104
|
+
why: "The issue carries the investigation; the decision belongs in a document that outlives it."
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
concept: "Question",
|
|
108
|
+
instead: "a Discussion, or `Research` when it needs work",
|
|
109
|
+
why: "A question that needs no work is not a backlog item."
|
|
110
|
+
}
|
|
111
|
+
];
|
|
112
|
+
var DEFAULT_LABELS = [
|
|
113
|
+
{ name: "api", description: "Public interfaces and contracts", color: "006b75" },
|
|
114
|
+
{ name: "backend", description: "Server-side code", color: "5319e7" },
|
|
115
|
+
{ name: "frontend", description: "User-facing code", color: "bfd4f2" },
|
|
116
|
+
{ name: "database", description: "Schema, queries and migrations", color: "c2e0c6" },
|
|
117
|
+
{ name: "infra", description: "Infrastructure, CI and deployment", color: "f9d0c4" },
|
|
118
|
+
{ name: "docs", description: "Documentation", color: "0075ca" },
|
|
119
|
+
{ name: "security", description: "Security or privacy impact", color: "d93f0b" },
|
|
120
|
+
{
|
|
121
|
+
name: "tech-debt",
|
|
122
|
+
description: "Debt paid down rather than capability added",
|
|
123
|
+
color: "e4e669"
|
|
124
|
+
},
|
|
125
|
+
{ name: "blocked", description: "Waiting on something outside this repository", color: "b60205" },
|
|
126
|
+
{ name: "good first issue", description: "Good for newcomers", color: "7057ff" }
|
|
127
|
+
];
|
|
128
|
+
var FALLBACK_TYPE_LABELS = DEFAULT_ISSUE_TYPES.map((type) => ({
|
|
129
|
+
name: `type:${type.slug}`,
|
|
130
|
+
description: type.summary,
|
|
131
|
+
color: type.executable ? "1d76db" : "d4c5f9"
|
|
132
|
+
}));
|
|
133
|
+
var DEFAULT_COMMIT_CONVENTION = "Conventional Commits \u2014 type(scope): subject";
|
|
134
|
+
|
|
135
|
+
// src/scaffold/assets.ts
|
|
136
|
+
function acceptanceCriteriaField() {
|
|
137
|
+
return [
|
|
138
|
+
" - type: textarea",
|
|
139
|
+
" id: acceptance",
|
|
140
|
+
" attributes:",
|
|
141
|
+
" label: Acceptance criteria",
|
|
142
|
+
" description: How anyone can tell this is done. Checks that fail today and pass afterwards.",
|
|
143
|
+
" value: |",
|
|
144
|
+
" - [ ] ",
|
|
145
|
+
" validations:",
|
|
146
|
+
" required: true"
|
|
147
|
+
].join("\n");
|
|
148
|
+
}
|
|
149
|
+
function referencesField() {
|
|
150
|
+
return [
|
|
151
|
+
" - type: textarea",
|
|
152
|
+
" id: references",
|
|
153
|
+
" attributes:",
|
|
154
|
+
" label: References",
|
|
155
|
+
" description: Links, related issues, prior discussion, evidence."
|
|
156
|
+
].join("\n");
|
|
157
|
+
}
|
|
158
|
+
function renderIssueForm(type) {
|
|
159
|
+
const header = [
|
|
160
|
+
`name: "${emojiFor(type.slug)} ${type.name}"`,
|
|
161
|
+
`description: "${type.summary}"`,
|
|
162
|
+
`type: "${type.name}"`,
|
|
163
|
+
"body:"
|
|
164
|
+
];
|
|
165
|
+
const bodies = {
|
|
166
|
+
idea: [
|
|
167
|
+
" - type: markdown",
|
|
168
|
+
" attributes:",
|
|
169
|
+
" value: |",
|
|
170
|
+
" Quick capture. Only the first field is required \u2014 triage fills in the rest.",
|
|
171
|
+
" An Idea is **not approved work**: no agent implements it.",
|
|
172
|
+
" - type: textarea",
|
|
173
|
+
" id: idea",
|
|
174
|
+
" attributes:",
|
|
175
|
+
" label: The idea",
|
|
176
|
+
" description: What it is, in a few lines. It does not have to be solved or complete.",
|
|
177
|
+
" validations:",
|
|
178
|
+
" required: true",
|
|
179
|
+
" - type: textarea",
|
|
180
|
+
" id: problem",
|
|
181
|
+
" attributes:",
|
|
182
|
+
" label: Problem or opportunity",
|
|
183
|
+
" description: What pain or gain is behind it? Leave blank if you do not know yet.",
|
|
184
|
+
referencesField(),
|
|
185
|
+
" - type: textarea",
|
|
186
|
+
" id: questions",
|
|
187
|
+
" attributes:",
|
|
188
|
+
" label: Open questions",
|
|
189
|
+
" description: What would have to be answered before deciding anything."
|
|
190
|
+
],
|
|
191
|
+
research: [
|
|
192
|
+
" - type: textarea",
|
|
193
|
+
" id: question",
|
|
194
|
+
" attributes:",
|
|
195
|
+
" label: Question to answer",
|
|
196
|
+
" description: The decision this investigation has to unblock.",
|
|
197
|
+
" validations:",
|
|
198
|
+
" required: true",
|
|
199
|
+
" - type: textarea",
|
|
200
|
+
" id: context",
|
|
201
|
+
" attributes:",
|
|
202
|
+
" label: Context",
|
|
203
|
+
" description: What is already known, and why the question is open.",
|
|
204
|
+
" - type: textarea",
|
|
205
|
+
" id: deliverable",
|
|
206
|
+
" attributes:",
|
|
207
|
+
" label: Expected deliverable",
|
|
208
|
+
" description: A conclusion and a recommendation. Say where durable knowledge will be written down.",
|
|
209
|
+
" validations:",
|
|
210
|
+
" required: true",
|
|
211
|
+
referencesField()
|
|
212
|
+
],
|
|
213
|
+
epic: [
|
|
214
|
+
" - type: markdown",
|
|
215
|
+
" attributes:",
|
|
216
|
+
" value: |",
|
|
217
|
+
" An Epic is never executed directly: it is delivered through its sub-issues.",
|
|
218
|
+
" - type: textarea",
|
|
219
|
+
" id: objective",
|
|
220
|
+
" attributes:",
|
|
221
|
+
" label: Objective",
|
|
222
|
+
" description: The state of the product this Epic describes.",
|
|
223
|
+
" validations:",
|
|
224
|
+
" required: true",
|
|
225
|
+
" - type: textarea",
|
|
226
|
+
" id: scope",
|
|
227
|
+
" attributes:",
|
|
228
|
+
" label: Scope",
|
|
229
|
+
" validations:",
|
|
230
|
+
" required: true",
|
|
231
|
+
" - type: textarea",
|
|
232
|
+
" id: out-of-scope",
|
|
233
|
+
" attributes:",
|
|
234
|
+
" label: Out of scope",
|
|
235
|
+
" description: What this Epic deliberately does not cover. Absence here is what makes scope creep arguable.",
|
|
236
|
+
" - type: textarea",
|
|
237
|
+
" id: children",
|
|
238
|
+
" attributes:",
|
|
239
|
+
" label: Known sub-issues",
|
|
240
|
+
" description: Add them as sub-issues once they exist; list what you already foresee."
|
|
241
|
+
],
|
|
242
|
+
feature: [
|
|
243
|
+
" - type: textarea",
|
|
244
|
+
" id: problem",
|
|
245
|
+
" attributes:",
|
|
246
|
+
" label: Problem",
|
|
247
|
+
" description: What is not possible today, and for whom.",
|
|
248
|
+
" validations:",
|
|
249
|
+
" required: true",
|
|
250
|
+
" - type: textarea",
|
|
251
|
+
" id: objective",
|
|
252
|
+
" attributes:",
|
|
253
|
+
" label: Objective",
|
|
254
|
+
" description: What becomes true when this is done.",
|
|
255
|
+
" validations:",
|
|
256
|
+
" required: true",
|
|
257
|
+
" - type: textarea",
|
|
258
|
+
" id: scope",
|
|
259
|
+
" attributes:",
|
|
260
|
+
" label: Scope and out of scope",
|
|
261
|
+
" description: What is included, and what is deliberately not.",
|
|
262
|
+
acceptanceCriteriaField(),
|
|
263
|
+
" - type: textarea",
|
|
264
|
+
" id: dependencies",
|
|
265
|
+
" attributes:",
|
|
266
|
+
" label: Dependencies and risks",
|
|
267
|
+
" description: What has to exist first, and what could go wrong.",
|
|
268
|
+
referencesField()
|
|
269
|
+
],
|
|
270
|
+
bug: [
|
|
271
|
+
" - type: textarea",
|
|
272
|
+
" id: current",
|
|
273
|
+
" attributes:",
|
|
274
|
+
" label: Current behaviour",
|
|
275
|
+
" validations:",
|
|
276
|
+
" required: true",
|
|
277
|
+
" - type: textarea",
|
|
278
|
+
" id: expected",
|
|
279
|
+
" attributes:",
|
|
280
|
+
" label: Expected behaviour",
|
|
281
|
+
" validations:",
|
|
282
|
+
" required: true",
|
|
283
|
+
" - type: textarea",
|
|
284
|
+
" id: steps",
|
|
285
|
+
" attributes:",
|
|
286
|
+
" label: Steps to reproduce",
|
|
287
|
+
" value: |",
|
|
288
|
+
" 1.",
|
|
289
|
+
" 2.",
|
|
290
|
+
" 3.",
|
|
291
|
+
" validations:",
|
|
292
|
+
" required: true",
|
|
293
|
+
" - type: input",
|
|
294
|
+
" id: environment",
|
|
295
|
+
" attributes:",
|
|
296
|
+
" label: Environment",
|
|
297
|
+
" description: Where it happens \u2014 environment, version, component, browser.",
|
|
298
|
+
" validations:",
|
|
299
|
+
" required: true",
|
|
300
|
+
" - type: textarea",
|
|
301
|
+
" id: evidence",
|
|
302
|
+
" attributes:",
|
|
303
|
+
" label: Evidence",
|
|
304
|
+
" description: Logs, request and response, screenshot, trace id, affected URL.",
|
|
305
|
+
" - type: dropdown",
|
|
306
|
+
" id: impact",
|
|
307
|
+
" attributes:",
|
|
308
|
+
" label: Impact",
|
|
309
|
+
" options:",
|
|
310
|
+
' - "Data loss or improper exposure"',
|
|
311
|
+
' - "Feature unavailable, no workaround"',
|
|
312
|
+
' - "Feature degraded, workaround exists"',
|
|
313
|
+
' - "Cosmetic or low consequence"',
|
|
314
|
+
" validations:",
|
|
315
|
+
" required: true",
|
|
316
|
+
" - type: textarea",
|
|
317
|
+
" id: done",
|
|
318
|
+
" attributes:",
|
|
319
|
+
" label: How to tell it is fixed",
|
|
320
|
+
" description: The check that fails today and passes afterwards.",
|
|
321
|
+
" validations:",
|
|
322
|
+
" required: true"
|
|
323
|
+
],
|
|
324
|
+
task: [
|
|
325
|
+
" - type: textarea",
|
|
326
|
+
" id: what",
|
|
327
|
+
" attributes:",
|
|
328
|
+
" label: What has to be done",
|
|
329
|
+
" validations:",
|
|
330
|
+
" required: true",
|
|
331
|
+
" - type: textarea",
|
|
332
|
+
" id: why",
|
|
333
|
+
" attributes:",
|
|
334
|
+
" label: Why",
|
|
335
|
+
" description: The reason this is worth doing now.",
|
|
336
|
+
" validations:",
|
|
337
|
+
" required: true",
|
|
338
|
+
acceptanceCriteriaField(),
|
|
339
|
+
referencesField()
|
|
340
|
+
]
|
|
341
|
+
};
|
|
342
|
+
const body = bodies[type.slug];
|
|
343
|
+
if (body === void 0) {
|
|
344
|
+
throw new Error(`No Issue Form body defined for type "${type.slug}"`);
|
|
345
|
+
}
|
|
346
|
+
return `${[...header, ...body].join("\n")}
|
|
347
|
+
`;
|
|
348
|
+
}
|
|
349
|
+
function emojiFor(slug) {
|
|
350
|
+
const map = {
|
|
351
|
+
idea: "\u{1F4A1}",
|
|
352
|
+
research: "\u{1F52C}",
|
|
353
|
+
epic: "\u{1F5FA}\uFE0F",
|
|
354
|
+
feature: "\u2728",
|
|
355
|
+
bug: "\u{1F41B}",
|
|
356
|
+
task: "\u{1F527}"
|
|
357
|
+
};
|
|
358
|
+
return map[slug] ?? "\u{1F4CC}";
|
|
359
|
+
}
|
|
360
|
+
function renderIssueTemplateConfig() {
|
|
361
|
+
return [
|
|
362
|
+
"blank_issues_enabled: true",
|
|
363
|
+
"contact_links:",
|
|
364
|
+
" - name: How this backlog works",
|
|
365
|
+
" url: https://github.com/fabioassuncao/issue-flow/blob/main/docs/conventions.md",
|
|
366
|
+
" about: Issue types, what each one authorizes, labels and the lifecycle.",
|
|
367
|
+
""
|
|
368
|
+
].join("\n");
|
|
369
|
+
}
|
|
370
|
+
function renderPullRequestTemplate() {
|
|
371
|
+
return [
|
|
372
|
+
"## What changed",
|
|
373
|
+
"",
|
|
374
|
+
"<!-- What this Pull Request does, and why. One paragraph is enough. -->",
|
|
375
|
+
"",
|
|
376
|
+
"## How it was tested",
|
|
377
|
+
"",
|
|
378
|
+
'<!-- The checks you ran, and what they proved. "CI is green" is not a test plan. -->',
|
|
379
|
+
"",
|
|
380
|
+
"## Related issues",
|
|
381
|
+
"",
|
|
382
|
+
"<!--",
|
|
383
|
+
"One `Closes #N` per issue this resolves, each on its own line and as plain body",
|
|
384
|
+
"text \u2014 GitHub only auto-closes when the line is not inside a code fence.",
|
|
385
|
+
"-->",
|
|
386
|
+
"",
|
|
387
|
+
"## Notes for reviewers",
|
|
388
|
+
"",
|
|
389
|
+
'<!-- Where to start, and anything you are unsure about. Write "N/A" rather than deleting a section. -->',
|
|
390
|
+
""
|
|
391
|
+
].join("\n");
|
|
392
|
+
}
|
|
393
|
+
function renderAgentsMd(projectName, referenced) {
|
|
394
|
+
const lines = [
|
|
395
|
+
`# ${projectName}`,
|
|
396
|
+
"",
|
|
397
|
+
"This file is an **index**. It holds no rule, command or convention of its own \u2014",
|
|
398
|
+
"those live in the documents referenced below, which are the source of truth.",
|
|
399
|
+
"",
|
|
400
|
+
"> Document once. Reference everywhere it is needed.",
|
|
401
|
+
"",
|
|
402
|
+
"## Start here",
|
|
403
|
+
""
|
|
404
|
+
];
|
|
405
|
+
for (const path of referenced) {
|
|
406
|
+
lines.push(`- [\`${path}\`](${path})`);
|
|
407
|
+
}
|
|
408
|
+
if (referenced.length === 0) {
|
|
409
|
+
lines.push("- [`README.md`](README.md) \u2014 what this project is, and how to run it");
|
|
410
|
+
}
|
|
411
|
+
lines.push(
|
|
412
|
+
"",
|
|
413
|
+
"## Working on an issue",
|
|
414
|
+
"",
|
|
415
|
+
"- [`docs/conventions.md`](docs/conventions.md) \u2014 issue types, labels, branches,",
|
|
416
|
+
" commits and Pull Requests: what this repository expects and what it does not",
|
|
417
|
+
"",
|
|
418
|
+
"## What does not belong in this file",
|
|
419
|
+
"",
|
|
420
|
+
"Anything that can live in a document of its own: build and test commands, code",
|
|
421
|
+
"style, architecture rules, testing strategy, operational procedures. If it is a",
|
|
422
|
+
"rule, a standard or reusable knowledge, it belongs in its own document and this",
|
|
423
|
+
"file only points at it.",
|
|
424
|
+
"",
|
|
425
|
+
"An instruction that today exists **only** here does not stay here: move it to the",
|
|
426
|
+
"right document and leave a reference behind. Duplicated instructions in an agent",
|
|
427
|
+
"file age out of sight and start contradicting the source without anyone noticing.",
|
|
428
|
+
""
|
|
429
|
+
);
|
|
430
|
+
return lines.join("\n");
|
|
431
|
+
}
|
|
432
|
+
function renderClaudeMd() {
|
|
433
|
+
return "Read and follow the instructions in AGENTS.md.\n";
|
|
434
|
+
}
|
|
435
|
+
function renderConventionsDoc(hasIssueTypes) {
|
|
436
|
+
const typeRows = DEFAULT_ISSUE_TYPES.map(
|
|
437
|
+
(t) => `| **${t.name}** | ${t.summary} | ${t.executable ? "Yes, once it is ready" : "**No**"} |`
|
|
438
|
+
).join("\n");
|
|
439
|
+
const nonTypeRows = NON_TYPES.map((n) => `| ${n.concept} | ${n.instead} | ${n.why} |`).join("\n");
|
|
440
|
+
const labelRows = DEFAULT_LABELS.map((l) => `| \`${l.name}\` | ${l.description} |`).join("\n");
|
|
441
|
+
const typeMechanism = hasIssueTypes ? "This organization has GitHub Issue Types, so the type lives in the **native field**." : [
|
|
442
|
+
"This organization has no GitHub Issue Types, so the type is carried by a",
|
|
443
|
+
"`type:*` label. If Issue Types are enabled later, move to them and drop these",
|
|
444
|
+
"labels \u2014 keeping both would create a second truth that drifts."
|
|
445
|
+
].join("\n");
|
|
446
|
+
return `# Conventions
|
|
447
|
+
|
|
448
|
+
How work is recorded, classified and executed in this repository.
|
|
449
|
+
|
|
450
|
+
## Principles
|
|
451
|
+
|
|
452
|
+
1. **An open issue is not approved work.** Recording is cheap; executing requires
|
|
453
|
+
explicit authorization.
|
|
454
|
+
2. **Native structure before textual convention.** The order of preference is
|
|
455
|
+
native feature > structured field > label > free text. Nothing GitHub already
|
|
456
|
+
models is re-implemented as a title prefix or a label.
|
|
457
|
+
3. **Each piece of information in one place.** If something has a field, it does
|
|
458
|
+
not also have a label.
|
|
459
|
+
4. **Capturing an idea is not bureaucratic.** The Idea form has one required
|
|
460
|
+
field. The cost of recording a thought has to stay near zero.
|
|
461
|
+
|
|
462
|
+
## Issue types
|
|
463
|
+
|
|
464
|
+
${typeMechanism}
|
|
465
|
+
|
|
466
|
+
| Type | What it is | Authorizes execution? |
|
|
467
|
+
|---|---|---|
|
|
468
|
+
${typeRows}
|
|
469
|
+
|
|
470
|
+
### Choosing the type
|
|
471
|
+
|
|
472
|
+
In order \u2014 the first "yes" decides:
|
|
473
|
+
|
|
474
|
+
1. **Is something broken?** \u2192 \`Bug\`, even if the expected behaviour never worked.
|
|
475
|
+
2. **Is the question still unanswered?** \u2192 \`Research\`.
|
|
476
|
+
3. **Is it just a record, with no analysis yet?** \u2192 \`Idea\`.
|
|
477
|
+
4. **Is it large and delivered in parts?** \u2192 \`Epic\`, with sub-issues.
|
|
478
|
+
5. **Does it change what the product does?** \u2192 \`Feature\`.
|
|
479
|
+
6. **None of the above** \u2192 \`Task\`.
|
|
480
|
+
|
|
481
|
+
### What is deliberately not a type
|
|
482
|
+
|
|
483
|
+
A type per flavour of work fragments the backlog without improving a single
|
|
484
|
+
query.
|
|
485
|
+
|
|
486
|
+
| Concept | Represent it as | Why |
|
|
487
|
+
|---|---|---|
|
|
488
|
+
${nonTypeRows}
|
|
489
|
+
|
|
490
|
+
## Labels
|
|
491
|
+
|
|
492
|
+
Labels are the last resort, for what has no native representation: technical
|
|
493
|
+
area, component and cross-cutting characteristic.
|
|
494
|
+
|
|
495
|
+
| Label | Meaning |
|
|
496
|
+
|---|---|
|
|
497
|
+
${labelRows}
|
|
498
|
+
|
|
499
|
+
**Never use a label for** priority, type, state or size when a field exists for
|
|
500
|
+
it: the label becomes a second truth that ages on its own.
|
|
501
|
+
|
|
502
|
+
## Branches and commits
|
|
503
|
+
|
|
504
|
+
- Branch: \`issue/{N}-{slug}\`
|
|
505
|
+
- Commit: ${DEFAULT_COMMIT_CONVENTION}
|
|
506
|
+
|
|
507
|
+
The commit type must match the nature of the change. A bug fix committed as
|
|
508
|
+
\`feat:\` corrupts the changelog and any version bump derived from the history.
|
|
509
|
+
|
|
510
|
+
## Pull Requests
|
|
511
|
+
|
|
512
|
+
Follow \`.github/PULL_REQUEST_TEMPLATE.md\`. Keep every section it defines and
|
|
513
|
+
answer the ones that do not apply with one line saying why \u2014 a deleted section
|
|
514
|
+
reads as an unanswered one to automated review.
|
|
515
|
+
|
|
516
|
+
Link the issues with one \`Closes #N\` per issue, as plain body text.
|
|
517
|
+
|
|
518
|
+
## Agent entry points
|
|
519
|
+
|
|
520
|
+
\`AGENTS.md\` is the canonical entry point for any coding agent, of any vendor.
|
|
521
|
+
It is an index: it says which documents to read, and holds no rule of its own.
|
|
522
|
+
|
|
523
|
+
\`CLAUDE.md\` exists only as the Claude Code integration and contains a single
|
|
524
|
+
line pointing at \`AGENTS.md\`. Any other agent adapter follows the same rule.
|
|
525
|
+
|
|
526
|
+
\`\`\`text
|
|
527
|
+
CLAUDE.md \u2192 AGENTS.md \u2192 specialized documentation \u2192 single source of truth
|
|
528
|
+
\`\`\`
|
|
529
|
+
`;
|
|
530
|
+
}
|
|
531
|
+
function renderLabelsFile(includeTypeLabels) {
|
|
532
|
+
const labels = includeTypeLabels ? [...FALLBACK_TYPE_LABELS, ...DEFAULT_LABELS] : DEFAULT_LABELS;
|
|
533
|
+
return `${JSON.stringify(
|
|
534
|
+
labels.map((label) => ({
|
|
535
|
+
name: label.name,
|
|
536
|
+
description: label.description,
|
|
537
|
+
color: label.color
|
|
538
|
+
})),
|
|
539
|
+
null,
|
|
540
|
+
2
|
|
541
|
+
)}
|
|
542
|
+
`;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// src/scaffold/plan.ts
|
|
546
|
+
function action(path, kind, tier, reason, content) {
|
|
547
|
+
return content === void 0 ? { path, kind, tier, reason } : { path, kind, tier, reason, content };
|
|
548
|
+
}
|
|
549
|
+
function isClaudeBridge(content) {
|
|
550
|
+
const meaningful = content.split("\n").map((line) => line.trim()).filter((line) => line !== "" && !line.startsWith("#"));
|
|
551
|
+
if (meaningful.length === 0 || meaningful.length > 2) return false;
|
|
552
|
+
return meaningful.every((line) => /agents\.md/i.test(line));
|
|
553
|
+
}
|
|
554
|
+
async function buildScaffoldPlan(state) {
|
|
555
|
+
const { policy } = state;
|
|
556
|
+
const actions = [];
|
|
557
|
+
const notes = [];
|
|
558
|
+
const localTemplates = policy.issues.templates.filter((t) => t.origin === "filesystem");
|
|
559
|
+
const orgTemplates = policy.issues.templates.filter((t) => t.origin === "organization");
|
|
560
|
+
if (localTemplates.length > 0) {
|
|
561
|
+
actions.push(
|
|
562
|
+
action(
|
|
563
|
+
".github/ISSUE_TEMPLATE/",
|
|
564
|
+
"keep",
|
|
565
|
+
"required",
|
|
566
|
+
`${localTemplates.length} Issue Template(s) already declared in this repository.`
|
|
567
|
+
)
|
|
568
|
+
);
|
|
569
|
+
} else if (orgTemplates.length > 0) {
|
|
570
|
+
actions.push(
|
|
571
|
+
action(
|
|
572
|
+
".github/ISSUE_TEMPLATE/",
|
|
573
|
+
"keep",
|
|
574
|
+
"required",
|
|
575
|
+
`The organization serves ${orgTemplates.length} Issue Template(s) to this repository; a local copy would fork them.`
|
|
576
|
+
)
|
|
577
|
+
);
|
|
578
|
+
notes.push(
|
|
579
|
+
"Issue Templates come from the organization. Change them there, not here \u2014 a local copy stops tracking the original the moment either is edited."
|
|
580
|
+
);
|
|
581
|
+
} else {
|
|
582
|
+
for (const type of DEFAULT_ISSUE_TYPES) {
|
|
583
|
+
const path = `.github/ISSUE_TEMPLATE/${type.order}-${type.slug}.yml`;
|
|
584
|
+
actions.push(
|
|
585
|
+
action(
|
|
586
|
+
path,
|
|
587
|
+
"create",
|
|
588
|
+
"required",
|
|
589
|
+
`Issue Form for the "${type.name}" type.`,
|
|
590
|
+
renderIssueForm(type)
|
|
591
|
+
)
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
const configPath = ".github/ISSUE_TEMPLATE/config.yml";
|
|
595
|
+
actions.push(
|
|
596
|
+
action(
|
|
597
|
+
configPath,
|
|
598
|
+
await state.exists(configPath) ? "keep" : "create",
|
|
599
|
+
"required",
|
|
600
|
+
"Template chooser: keeps blank issues enabled and links the conventions.",
|
|
601
|
+
renderIssueTemplateConfig()
|
|
602
|
+
)
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
if (policy.pullRequests.templates.length > 0) {
|
|
606
|
+
actions.push(
|
|
607
|
+
action(
|
|
608
|
+
policy.pullRequests.templates[0]?.path ?? ".github/PULL_REQUEST_TEMPLATE.md",
|
|
609
|
+
"keep",
|
|
610
|
+
"required",
|
|
611
|
+
"A Pull Request template already exists."
|
|
612
|
+
)
|
|
613
|
+
);
|
|
614
|
+
} else {
|
|
615
|
+
actions.push(
|
|
616
|
+
action(
|
|
617
|
+
".github/PULL_REQUEST_TEMPLATE.md",
|
|
618
|
+
"create",
|
|
619
|
+
"required",
|
|
620
|
+
"Gives every Pull Request a body that review can rely on.",
|
|
621
|
+
renderPullRequestTemplate()
|
|
622
|
+
)
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
const agents = policy.docs.find((doc) => doc.kind === "agents" && doc.scope === "");
|
|
626
|
+
const claude = policy.docs.find((doc) => doc.kind === "claude" && doc.scope === "");
|
|
627
|
+
const referenced = policy.docs.filter((doc) => doc.kind === "contributing" || doc.kind === "referenced").map((doc) => doc.path);
|
|
628
|
+
if (agents === void 0) {
|
|
629
|
+
actions.push(
|
|
630
|
+
action(
|
|
631
|
+
"AGENTS.md",
|
|
632
|
+
"create",
|
|
633
|
+
"required",
|
|
634
|
+
"The canonical entry point for any coding agent, as an index of the real documentation.",
|
|
635
|
+
renderAgentsMd(state.projectName, referenced)
|
|
636
|
+
)
|
|
637
|
+
);
|
|
638
|
+
} else {
|
|
639
|
+
actions.push(action("AGENTS.md", "keep", "required", "Already the canonical entry point."));
|
|
640
|
+
}
|
|
641
|
+
if (claude === void 0) {
|
|
642
|
+
actions.push(
|
|
643
|
+
action(
|
|
644
|
+
"CLAUDE.md",
|
|
645
|
+
"create",
|
|
646
|
+
"recommended",
|
|
647
|
+
"One-line bridge to AGENTS.md, so Claude Code reads the canonical source.",
|
|
648
|
+
renderClaudeMd()
|
|
649
|
+
)
|
|
650
|
+
);
|
|
651
|
+
} else if (isClaudeBridge(claude.content)) {
|
|
652
|
+
actions.push(action("CLAUDE.md", "keep", "recommended", "Already a bridge to AGENTS.md."));
|
|
653
|
+
} else if (agents === void 0) {
|
|
654
|
+
notes.push(
|
|
655
|
+
"CLAUDE.md carries its own instructions and there is no AGENTS.md. Move that content into AGENTS.md and reduce CLAUDE.md to the one-line bridge; nothing here rewrites it for you."
|
|
656
|
+
);
|
|
657
|
+
actions.push(
|
|
658
|
+
action(
|
|
659
|
+
"CLAUDE.md",
|
|
660
|
+
"review",
|
|
661
|
+
"recommended",
|
|
662
|
+
"Holds instructions of its own; AGENTS.md should be the canonical source."
|
|
663
|
+
)
|
|
664
|
+
);
|
|
665
|
+
} else {
|
|
666
|
+
notes.push(
|
|
667
|
+
"AGENTS.md and CLAUDE.md both carry instructions. Two copies of the same rules diverge the first time only one is edited \u2014 keep AGENTS.md and reduce CLAUDE.md to the one-line bridge."
|
|
668
|
+
);
|
|
669
|
+
actions.push(
|
|
670
|
+
action(
|
|
671
|
+
"CLAUDE.md",
|
|
672
|
+
"review",
|
|
673
|
+
"recommended",
|
|
674
|
+
"Duplicates instructions that AGENTS.md already owns."
|
|
675
|
+
)
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
const hasContributing = policy.docs.some((doc) => doc.kind === "contributing");
|
|
679
|
+
const conventionsPath = "docs/conventions.md";
|
|
680
|
+
const conventionsExists = await state.exists(conventionsPath);
|
|
681
|
+
if (conventionsExists) {
|
|
682
|
+
actions.push(
|
|
683
|
+
action(conventionsPath, "keep", "recommended", "Conventions are already documented.")
|
|
684
|
+
);
|
|
685
|
+
} else if (orgTemplates.length > 0) {
|
|
686
|
+
actions.push(
|
|
687
|
+
action(
|
|
688
|
+
conventionsPath,
|
|
689
|
+
"keep",
|
|
690
|
+
"recommended",
|
|
691
|
+
"The conventions are the organization's; documenting them again here would fork them."
|
|
692
|
+
)
|
|
693
|
+
);
|
|
694
|
+
} else if (hasContributing && localTemplates.length > 0) {
|
|
695
|
+
actions.push(
|
|
696
|
+
action(
|
|
697
|
+
conventionsPath,
|
|
698
|
+
"keep",
|
|
699
|
+
"recommended",
|
|
700
|
+
"CONTRIBUTING.md and the Issue Templates already document how this repository works."
|
|
701
|
+
)
|
|
702
|
+
);
|
|
703
|
+
} else {
|
|
704
|
+
actions.push(
|
|
705
|
+
action(
|
|
706
|
+
conventionsPath,
|
|
707
|
+
"create",
|
|
708
|
+
"recommended",
|
|
709
|
+
"The source of truth for issue types, labels, branches and commits.",
|
|
710
|
+
renderConventionsDoc(policy.issues.types.length > 0)
|
|
711
|
+
)
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
const labelsPath = ".github/labels.json";
|
|
715
|
+
if (policy.issues.labels.length > 0) {
|
|
716
|
+
actions.push(
|
|
717
|
+
action(
|
|
718
|
+
labelsPath,
|
|
719
|
+
"keep",
|
|
720
|
+
"contextual",
|
|
721
|
+
`This repository already has ${policy.issues.labels.length} label(s); Issue Flow never rewrites a taxonomy.`
|
|
722
|
+
)
|
|
723
|
+
);
|
|
724
|
+
} else if (await state.exists(labelsPath)) {
|
|
725
|
+
actions.push(action(labelsPath, "keep", "contextual", "A labels file already exists."));
|
|
726
|
+
} else {
|
|
727
|
+
const includeTypeLabels = policy.issues.types.length === 0;
|
|
728
|
+
actions.push(
|
|
729
|
+
action(
|
|
730
|
+
labelsPath,
|
|
731
|
+
"create",
|
|
732
|
+
"contextual",
|
|
733
|
+
includeTypeLabels ? "A baseline label set, including type labels because this organization has no Issue Types." : "A baseline label set. Type labels are omitted: this organization has native Issue Types.",
|
|
734
|
+
renderLabelsFile(includeTypeLabels)
|
|
735
|
+
)
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
if (policy.issues.types.length === 0) {
|
|
739
|
+
notes.push(
|
|
740
|
+
"No GitHub Issue Types were found. They are an organization-level setting and cannot be created from here; defining them there is better than the `type:*` labels this proposes as a fallback."
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
return { root: policy.root, actions, notes };
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// src/scaffold/apply.ts
|
|
747
|
+
async function exists(path) {
|
|
748
|
+
try {
|
|
749
|
+
await access(path);
|
|
750
|
+
return true;
|
|
751
|
+
} catch {
|
|
752
|
+
return false;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
async function applyScaffoldPlan(plan) {
|
|
756
|
+
const written = [];
|
|
757
|
+
const skipped = [];
|
|
758
|
+
for (const item of plan.actions) {
|
|
759
|
+
if (item.kind !== "create" || item.content === void 0) continue;
|
|
760
|
+
const absolute = join2(plan.root, item.path);
|
|
761
|
+
if (await exists(absolute)) {
|
|
762
|
+
skipped.push(item.path);
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
await mkdir(dirname(absolute), { recursive: true });
|
|
766
|
+
await writeFile(absolute, item.content, "utf-8");
|
|
767
|
+
written.push(item.path);
|
|
768
|
+
}
|
|
769
|
+
return { written, skipped };
|
|
770
|
+
}
|
|
771
|
+
async function resolveRepositoryState(options = {}) {
|
|
772
|
+
const root = options.root ?? await getProjectRoot();
|
|
773
|
+
const policy = await loadRepositoryPolicy({ root, scope: options.scope ?? null });
|
|
774
|
+
return {
|
|
775
|
+
policy,
|
|
776
|
+
projectName: basename(root),
|
|
777
|
+
exists: (relPath) => exists(join2(root, relPath))
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
async function planRepositoryScaffold(options = {}) {
|
|
781
|
+
return buildScaffoldPlan(await resolveRepositoryState(options));
|
|
782
|
+
}
|
|
783
|
+
export {
|
|
784
|
+
applyScaffoldPlan,
|
|
785
|
+
planRepositoryScaffold,
|
|
786
|
+
resolveRepositoryState
|
|
787
|
+
};
|
|
788
|
+
//# sourceMappingURL=apply-WUJHDD7A.js.map
|