agent-templates 0.1.0 → 0.2.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/ADOPTING.md +57 -57
- package/CLAUDE.md +128 -125
- package/LICENSE +21 -21
- package/README.md +32 -32
- package/package.json +32 -32
- package/patterns/three-agent-architect-builder-reviewer/README.md +161 -153
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/agents/architect.md +31 -31
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/agents/builder.md +25 -25
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/agents/reviewer.md +33 -33
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/agents/triage.md +31 -31
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/commands/breakdown-prd.md +18 -18
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/commands/nightly-issues.md +14 -14
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/commands/review-ticket.md +14 -14
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/commands/start-all.md +17 -0
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/commands/start-milestone.md +15 -15
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/commands/verify-delivery.md +20 -20
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/hooks/guard-main-session-writes.mjs +53 -53
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/scripts/milestone-dag.mjs +94 -0
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/scripts/publish-tickets.mjs +263 -263
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/settings.json +15 -15
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/workflows/nightly-issues.js +139 -139
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/workflows/run-milestone.js +223 -223
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/workflows/start-all.js +118 -0
- package/patterns/three-agent-architect-builder-reviewer/scaffold/INSTALL.md +65 -65
- package/patterns/three-agent-architect-builder-reviewer/scaffold/claude-md-snippet.md +38 -37
- package/scripts/adopt.mjs +187 -177
- package/scripts/build-site.mjs +264 -264
- package/scripts/cli.mjs +52 -52
- package/templates/pattern-README.template.md +65 -65
- package/templates/ticket.template.md +104 -104
- package/templates/tracker/github/ISSUE_TEMPLATE/bug-report.md +28 -28
- package/templates/tracker/github/ISSUE_TEMPLATE/decision-record.md +25 -25
- package/templates/tracker/github/ISSUE_TEMPLATE/task.md +36 -36
- package/templates/tracker/github/PULL_REQUEST_TEMPLATE.md +54 -54
- package/templates/tracker/gitlab/issue_templates/bug-report.md +23 -23
- package/templates/tracker/gitlab/issue_templates/decision-record.md +20 -20
- package/templates/tracker/gitlab/issue_templates/task.md +31 -31
- package/templates/tracker/gitlab/merge_request_templates/default.md +55 -55
package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/workflows/nightly-issues.js
CHANGED
|
@@ -1,139 +1,139 @@
|
|
|
1
|
-
export const meta = {
|
|
2
|
-
name: 'nightly-issues',
|
|
3
|
-
description: 'Nightly sweep: triage open issues, run fixable ones through the three-agent pipeline, post the morning report',
|
|
4
|
-
phases: [{ title: 'Triage' }, { title: 'Fix' }, { title: 'Report' }],
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
// Deterministic nightly orchestrator. The command (/nightly-issues) collects the
|
|
8
|
-
// issue list and date (Date is unavailable in workflow scripts) and passes:
|
|
9
|
-
//
|
|
10
|
-
// args:
|
|
11
|
-
// {
|
|
12
|
-
// issues: [{ number, title, body, labels: [..], isNew: bool, url }],
|
|
13
|
-
// maxIssues: 5, // optional cost cap per night
|
|
14
|
-
// defaultBranch: 'main', // optional
|
|
15
|
-
// platform: 'gh' | 'glab', // default 'gh'
|
|
16
|
-
// reportDate: 'YYYY-MM-DD', // required — stamped by the caller
|
|
17
|
-
// }
|
|
18
|
-
//
|
|
19
|
-
// Design: triage never touches the tracker; the pipeline (child run-milestone
|
|
20
|
-
// workflow) does the fixing with continueOnFailure=true (issues are independent,
|
|
21
|
-
// unlike milestone tickets); a single report step does ALL tracker writes at the
|
|
22
|
-
// end — comments, labels, and the "Nightly report <date>" issue the human reads
|
|
23
|
-
// over morning email (tracker notifications).
|
|
24
|
-
|
|
25
|
-
const cfg = Object.assign({ maxIssues: 5, defaultBranch: 'main', platform: 'gh' }, args)
|
|
26
|
-
if (!Array.isArray(cfg.issues)) throw new Error('args.issues must be an array (may be empty)')
|
|
27
|
-
if (typeof cfg.reportDate !== 'string' || !cfg.reportDate) throw new Error('args.reportDate is required (the caller stamps the date)')
|
|
28
|
-
if (!Number.isInteger(cfg.maxIssues) || cfg.maxIssues < 1) throw new Error('args.maxIssues must be an integer >= 1')
|
|
29
|
-
if (cfg.platform !== 'gh' && cfg.platform !== 'glab') throw new Error("args.platform must be 'gh' or 'glab'")
|
|
30
|
-
|
|
31
|
-
const SKIP_LABELS = ['nightly:escalated', 'triage:invalid', 'needs-human']
|
|
32
|
-
const eligible = cfg.issues.filter(function (i) {
|
|
33
|
-
return i && Number.isInteger(i.number) && !(i.labels || []).some(function (l) { return SKIP_LABELS.includes(l) })
|
|
34
|
-
})
|
|
35
|
-
const candidates = eligible.slice(0, cfg.maxIssues)
|
|
36
|
-
if (eligible.length > candidates.length) {
|
|
37
|
-
log('cap: processing ' + candidates.length + '/' + eligible.length + ' eligible issues (maxIssues=' + cfg.maxIssues + '); the rest wait for the next night')
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const TRIAGE = {
|
|
41
|
-
type: 'object',
|
|
42
|
-
properties: {
|
|
43
|
-
classification: { enum: ['fixable', 'invalid', 'needs-human'] },
|
|
44
|
-
reason: { type: 'string' },
|
|
45
|
-
ticketPath: { type: 'string' },
|
|
46
|
-
},
|
|
47
|
-
required: ['classification', 'reason'],
|
|
48
|
-
}
|
|
49
|
-
const REPORT = {
|
|
50
|
-
type: 'object',
|
|
51
|
-
properties: { reportUrl: { type: 'string' }, notes: { type: 'string' } },
|
|
52
|
-
required: ['reportUrl'],
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// ---- Phase 1: triage (parallel, read-only + ticket synthesis) ----
|
|
56
|
-
const triaged = await parallel(candidates.map(function (issue) {
|
|
57
|
-
return function () {
|
|
58
|
-
return agent(
|
|
59
|
-
'Triage this tracker issue per your role definition.\n' +
|
|
60
|
-
'Issue #' + issue.number + ': ' + issue.title + '\n' +
|
|
61
|
-
'Labels: ' + JSON.stringify(issue.labels || []) + '\n' +
|
|
62
|
-
'Body:\n' + (issue.body || '(empty)') + '\n\n' +
|
|
63
|
-
'If fixable, the ticket file MUST be docs/prd/99-nightly/tickets/ISS-' + issue.number + '-<slug>.md with id ISS-' + issue.number + '.',
|
|
64
|
-
{ agentType: 'triage', label: 'triage:#' + issue.number, phase: 'Triage', schema: TRIAGE }
|
|
65
|
-
).then(function (t) {
|
|
66
|
-
return { issue: issue, triage: t || { classification: 'needs-human', reason: 'triage agent returned nothing' } }
|
|
67
|
-
})
|
|
68
|
-
}
|
|
69
|
-
}))
|
|
70
|
-
const outcomes = triaged.filter(Boolean)
|
|
71
|
-
|
|
72
|
-
const fixable = outcomes.filter(function (o) { return o.triage.classification === 'fixable' && o.triage.ticketPath })
|
|
73
|
-
for (const o of outcomes) {
|
|
74
|
-
if (o.triage.classification === 'fixable' && !o.triage.ticketPath) {
|
|
75
|
-
o.triage.classification = 'needs-human'
|
|
76
|
-
o.triage.reason = 'classified fixable but returned no ticket path — ' + o.triage.reason
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
log('triage: ' + fixable.length + ' fixable, ' +
|
|
80
|
-
outcomes.filter(function (o) { return o.triage.classification === 'invalid' }).length + ' invalid, ' +
|
|
81
|
-
outcomes.filter(function (o) { return o.triage.classification === 'needs-human' }).length + ' needs-human')
|
|
82
|
-
|
|
83
|
-
// ---- Phase 2: fix via the milestone runner (child workflow, one nesting level) ----
|
|
84
|
-
let pipeline_results = []
|
|
85
|
-
if (fixable.length > 0) {
|
|
86
|
-
const tickets = fixable.map(function (o) {
|
|
87
|
-
return { id: 'ISS-' + o.issue.number, path: o.triage.ticketPath, issue: o.issue.number }
|
|
88
|
-
})
|
|
89
|
-
try {
|
|
90
|
-
const child = await workflow('run-milestone', {
|
|
91
|
-
tickets: tickets,
|
|
92
|
-
mode: 'autonomous',
|
|
93
|
-
defaultBranch: cfg.defaultBranch,
|
|
94
|
-
platform: cfg.platform,
|
|
95
|
-
continueOnFailure: true, // nightly issues are independent; one failure must not strand the rest
|
|
96
|
-
})
|
|
97
|
-
pipeline_results = (child && child.results) || []
|
|
98
|
-
} catch (e) {
|
|
99
|
-
log('pipeline failed to run: ' + (e && e.message ? e.message : String(e)))
|
|
100
|
-
pipeline_results = tickets.map(function (t) { return { id: t.id, status: 'failed', stage: 'pipeline', detail: 'milestone runner did not run' } })
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
const resultFor = function (issueNumber) {
|
|
104
|
-
return pipeline_results.find(function (r) { return r.id === 'ISS-' + issueNumber }) || null
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// ---- Phase 3: report (the ONLY tracker-writing step) ----
|
|
108
|
-
const digest = outcomes.map(function (o) {
|
|
109
|
-
const r = resultFor(o.issue.number)
|
|
110
|
-
return {
|
|
111
|
-
number: o.issue.number,
|
|
112
|
-
title: o.issue.title,
|
|
113
|
-
isNew: Boolean(o.issue.isNew),
|
|
114
|
-
classification: o.triage.classification,
|
|
115
|
-
reason: o.triage.reason,
|
|
116
|
-
pipeline: r ? { status: r.status, stage: r.stage || null, bounces: r.bounces || 0, detail: r.detail || null } : null,
|
|
117
|
-
}
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
const report = await agent(
|
|
121
|
-
'You are the nightly report step — the only step allowed to write to the tracker (' + cfg.platform + ' CLI via Bash). ' +
|
|
122
|
-
'Process this digest of tonight\'s sweep (JSON): ' + JSON.stringify({ date: cfg.reportDate, digest: digest, notProcessed: eligible.length - candidates.length }) + '\n' +
|
|
123
|
-
'Do, in order:\n' +
|
|
124
|
-
'1. Per issue: post ONE comment stating the outcome — fixed (link the merge) / attempted-but-not-solved (why, findings) / needs-human (why) / invalid (why). Base every statement on the digest; fabricate nothing.\n' +
|
|
125
|
-
'2. Labels: add `triage:invalid` to invalid issues (do NOT close them — the human decides in the morning); add `nightly:escalated` to attempted-but-not-solved and pipeline-failed issues; add `needs-human` to needs-human issues.\n' +
|
|
126
|
-
'3. For issues whose pipeline status is `delivered`: verify the issue is actually closed; close it if the deliver step missed it.\n' +
|
|
127
|
-
'4. Create a tracker issue titled exactly "Nightly report ' + cfg.reportDate + '" (search first; if it already exists, comment on it instead of duplicating) with sections: New overnight · Fixed & closed · Attempted, not solved · Needs human · Invalid (ignore) · Not processed (cap). Use issue references (#N) so the tracker links them.\n' +
|
|
128
|
-
'Return reportUrl and notes (anything that went wrong).',
|
|
129
|
-
{ label: 'report:' + cfg.reportDate, phase: 'Report', schema: REPORT }
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
return {
|
|
133
|
-
date: cfg.reportDate,
|
|
134
|
-
processed: digest,
|
|
135
|
-
notProcessed: eligible.length - candidates.length,
|
|
136
|
-
fixed: digest.filter(function (d) { return d.pipeline && d.pipeline.status === 'delivered' }).length,
|
|
137
|
-
reportUrl: report ? report.reportUrl : null,
|
|
138
|
-
reportNotes: report ? report.notes : 'report step returned nothing',
|
|
139
|
-
}
|
|
1
|
+
export const meta = {
|
|
2
|
+
name: 'nightly-issues',
|
|
3
|
+
description: 'Nightly sweep: triage open issues, run fixable ones through the three-agent pipeline, post the morning report',
|
|
4
|
+
phases: [{ title: 'Triage' }, { title: 'Fix' }, { title: 'Report' }],
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// Deterministic nightly orchestrator. The command (/nightly-issues) collects the
|
|
8
|
+
// issue list and date (Date is unavailable in workflow scripts) and passes:
|
|
9
|
+
//
|
|
10
|
+
// args:
|
|
11
|
+
// {
|
|
12
|
+
// issues: [{ number, title, body, labels: [..], isNew: bool, url }],
|
|
13
|
+
// maxIssues: 5, // optional cost cap per night
|
|
14
|
+
// defaultBranch: 'main', // optional
|
|
15
|
+
// platform: 'gh' | 'glab', // default 'gh'
|
|
16
|
+
// reportDate: 'YYYY-MM-DD', // required — stamped by the caller
|
|
17
|
+
// }
|
|
18
|
+
//
|
|
19
|
+
// Design: triage never touches the tracker; the pipeline (child run-milestone
|
|
20
|
+
// workflow) does the fixing with continueOnFailure=true (issues are independent,
|
|
21
|
+
// unlike milestone tickets); a single report step does ALL tracker writes at the
|
|
22
|
+
// end — comments, labels, and the "Nightly report <date>" issue the human reads
|
|
23
|
+
// over morning email (tracker notifications).
|
|
24
|
+
|
|
25
|
+
const cfg = Object.assign({ maxIssues: 5, defaultBranch: 'main', platform: 'gh' }, args)
|
|
26
|
+
if (!Array.isArray(cfg.issues)) throw new Error('args.issues must be an array (may be empty)')
|
|
27
|
+
if (typeof cfg.reportDate !== 'string' || !cfg.reportDate) throw new Error('args.reportDate is required (the caller stamps the date)')
|
|
28
|
+
if (!Number.isInteger(cfg.maxIssues) || cfg.maxIssues < 1) throw new Error('args.maxIssues must be an integer >= 1')
|
|
29
|
+
if (cfg.platform !== 'gh' && cfg.platform !== 'glab') throw new Error("args.platform must be 'gh' or 'glab'")
|
|
30
|
+
|
|
31
|
+
const SKIP_LABELS = ['nightly:escalated', 'triage:invalid', 'needs-human']
|
|
32
|
+
const eligible = cfg.issues.filter(function (i) {
|
|
33
|
+
return i && Number.isInteger(i.number) && !(i.labels || []).some(function (l) { return SKIP_LABELS.includes(l) })
|
|
34
|
+
})
|
|
35
|
+
const candidates = eligible.slice(0, cfg.maxIssues)
|
|
36
|
+
if (eligible.length > candidates.length) {
|
|
37
|
+
log('cap: processing ' + candidates.length + '/' + eligible.length + ' eligible issues (maxIssues=' + cfg.maxIssues + '); the rest wait for the next night')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const TRIAGE = {
|
|
41
|
+
type: 'object',
|
|
42
|
+
properties: {
|
|
43
|
+
classification: { enum: ['fixable', 'invalid', 'needs-human'] },
|
|
44
|
+
reason: { type: 'string' },
|
|
45
|
+
ticketPath: { type: 'string' },
|
|
46
|
+
},
|
|
47
|
+
required: ['classification', 'reason'],
|
|
48
|
+
}
|
|
49
|
+
const REPORT = {
|
|
50
|
+
type: 'object',
|
|
51
|
+
properties: { reportUrl: { type: 'string' }, notes: { type: 'string' } },
|
|
52
|
+
required: ['reportUrl'],
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ---- Phase 1: triage (parallel, read-only + ticket synthesis) ----
|
|
56
|
+
const triaged = await parallel(candidates.map(function (issue) {
|
|
57
|
+
return function () {
|
|
58
|
+
return agent(
|
|
59
|
+
'Triage this tracker issue per your role definition.\n' +
|
|
60
|
+
'Issue #' + issue.number + ': ' + issue.title + '\n' +
|
|
61
|
+
'Labels: ' + JSON.stringify(issue.labels || []) + '\n' +
|
|
62
|
+
'Body:\n' + (issue.body || '(empty)') + '\n\n' +
|
|
63
|
+
'If fixable, the ticket file MUST be docs/prd/99-nightly/tickets/ISS-' + issue.number + '-<slug>.md with id ISS-' + issue.number + '.',
|
|
64
|
+
{ agentType: 'triage', label: 'triage:#' + issue.number, phase: 'Triage', schema: TRIAGE }
|
|
65
|
+
).then(function (t) {
|
|
66
|
+
return { issue: issue, triage: t || { classification: 'needs-human', reason: 'triage agent returned nothing' } }
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
}))
|
|
70
|
+
const outcomes = triaged.filter(Boolean)
|
|
71
|
+
|
|
72
|
+
const fixable = outcomes.filter(function (o) { return o.triage.classification === 'fixable' && o.triage.ticketPath })
|
|
73
|
+
for (const o of outcomes) {
|
|
74
|
+
if (o.triage.classification === 'fixable' && !o.triage.ticketPath) {
|
|
75
|
+
o.triage.classification = 'needs-human'
|
|
76
|
+
o.triage.reason = 'classified fixable but returned no ticket path — ' + o.triage.reason
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
log('triage: ' + fixable.length + ' fixable, ' +
|
|
80
|
+
outcomes.filter(function (o) { return o.triage.classification === 'invalid' }).length + ' invalid, ' +
|
|
81
|
+
outcomes.filter(function (o) { return o.triage.classification === 'needs-human' }).length + ' needs-human')
|
|
82
|
+
|
|
83
|
+
// ---- Phase 2: fix via the milestone runner (child workflow, one nesting level) ----
|
|
84
|
+
let pipeline_results = []
|
|
85
|
+
if (fixable.length > 0) {
|
|
86
|
+
const tickets = fixable.map(function (o) {
|
|
87
|
+
return { id: 'ISS-' + o.issue.number, path: o.triage.ticketPath, issue: o.issue.number }
|
|
88
|
+
})
|
|
89
|
+
try {
|
|
90
|
+
const child = await workflow('run-milestone', {
|
|
91
|
+
tickets: tickets,
|
|
92
|
+
mode: 'autonomous',
|
|
93
|
+
defaultBranch: cfg.defaultBranch,
|
|
94
|
+
platform: cfg.platform,
|
|
95
|
+
continueOnFailure: true, // nightly issues are independent; one failure must not strand the rest
|
|
96
|
+
})
|
|
97
|
+
pipeline_results = (child && child.results) || []
|
|
98
|
+
} catch (e) {
|
|
99
|
+
log('pipeline failed to run: ' + (e && e.message ? e.message : String(e)))
|
|
100
|
+
pipeline_results = tickets.map(function (t) { return { id: t.id, status: 'failed', stage: 'pipeline', detail: 'milestone runner did not run' } })
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const resultFor = function (issueNumber) {
|
|
104
|
+
return pipeline_results.find(function (r) { return r.id === 'ISS-' + issueNumber }) || null
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ---- Phase 3: report (the ONLY tracker-writing step) ----
|
|
108
|
+
const digest = outcomes.map(function (o) {
|
|
109
|
+
const r = resultFor(o.issue.number)
|
|
110
|
+
return {
|
|
111
|
+
number: o.issue.number,
|
|
112
|
+
title: o.issue.title,
|
|
113
|
+
isNew: Boolean(o.issue.isNew),
|
|
114
|
+
classification: o.triage.classification,
|
|
115
|
+
reason: o.triage.reason,
|
|
116
|
+
pipeline: r ? { status: r.status, stage: r.stage || null, bounces: r.bounces || 0, detail: r.detail || null } : null,
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
const report = await agent(
|
|
121
|
+
'You are the nightly report step — the only step allowed to write to the tracker (' + cfg.platform + ' CLI via Bash). ' +
|
|
122
|
+
'Process this digest of tonight\'s sweep (JSON): ' + JSON.stringify({ date: cfg.reportDate, digest: digest, notProcessed: eligible.length - candidates.length }) + '\n' +
|
|
123
|
+
'Do, in order:\n' +
|
|
124
|
+
'1. Per issue: post ONE comment stating the outcome — fixed (link the merge) / attempted-but-not-solved (why, findings) / needs-human (why) / invalid (why). Base every statement on the digest; fabricate nothing.\n' +
|
|
125
|
+
'2. Labels: add `triage:invalid` to invalid issues (do NOT close them — the human decides in the morning); add `nightly:escalated` to attempted-but-not-solved and pipeline-failed issues; add `needs-human` to needs-human issues.\n' +
|
|
126
|
+
'3. For issues whose pipeline status is `delivered`: verify the issue is actually closed; close it if the deliver step missed it.\n' +
|
|
127
|
+
'4. Create a tracker issue titled exactly "Nightly report ' + cfg.reportDate + '" (search first; if it already exists, comment on it instead of duplicating) with sections: New overnight · Fixed & closed · Attempted, not solved · Needs human · Invalid (ignore) · Not processed (cap). Use issue references (#N) so the tracker links them.\n' +
|
|
128
|
+
'Return reportUrl and notes (anything that went wrong).',
|
|
129
|
+
{ label: 'report:' + cfg.reportDate, phase: 'Report', schema: REPORT }
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
date: cfg.reportDate,
|
|
134
|
+
processed: digest,
|
|
135
|
+
notProcessed: eligible.length - candidates.length,
|
|
136
|
+
fixed: digest.filter(function (d) { return d.pipeline && d.pipeline.status === 'delivered' }).length,
|
|
137
|
+
reportUrl: report ? report.reportUrl : null,
|
|
138
|
+
reportNotes: report ? report.notes : 'report step returned nothing',
|
|
139
|
+
}
|