changeledger 0.8.0 → 0.10.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/AGENTS.md +11 -3
- package/README.md +10 -1
- package/bin/changeledger.mjs +204 -21
- package/package.json +1 -1
- package/src/check.mjs +12 -0
- package/src/commands/agent-context.mjs +65 -0
- package/src/commands/agent-prompt.mjs +22 -0
- package/src/commands/agent.mjs +13 -10
- package/src/commands/check.mjs +55 -0
- package/src/commands/commit.mjs +39 -0
- package/src/commands/context.mjs +60 -27
- package/src/commands/fix.mjs +72 -0
- package/src/commands/register.mjs +9 -1
- package/src/commands/search.mjs +56 -0
- package/src/config-migration.mjs +44 -9
- package/src/config.mjs +13 -0
- package/src/contract.mjs +73 -15
- package/src/fix.mjs +127 -0
- package/src/framing.mjs +30 -0
- package/src/git.mjs +119 -2
- package/src/lifecycle.mjs +2 -2
- package/src/metrics.mjs +42 -0
- package/src/search.mjs +162 -0
- package/src/viewer/domain.mjs +2 -2
- package/src/viewer/public/app-state.js +62 -6
- package/src/viewer/public/app.js +138 -39
- package/src/viewer/public/index.html +2 -2
- package/src/viewer/public/state.js +6 -2
- package/src/viewer/public/styles.css +76 -5
- package/src/viewer/public/view-renderers.js +160 -48
- package/src/viewer/server/router.mjs +40 -6
- package/templates/config.yml +7 -1
- package/templates/contract/agent-contexts/audit.md +22 -0
- package/templates/contract/agent-contexts/implementation.md +18 -0
- package/templates/contract/agent-contexts/investigation.md +14 -0
- package/templates/contract/agent-contexts/review.md +22 -0
- package/templates/contract/agent-prompts/audit.md +37 -0
- package/templates/contract/agent-prompts/implementation.md +42 -0
- package/templates/contract/agent-prompts/investigation.md +41 -0
- package/templates/contract/agent-prompts/review.md +36 -0
- package/templates/contract/budgets.yml +16 -0
- package/templates/contract/close.md +19 -14
- package/templates/contract/core.md +41 -30
- package/templates/contract/delegation.md +2 -1
- package/templates/contract/implement.md +24 -10
- package/templates/contract/review.md +7 -9
- package/templates/contract/spec.md +14 -1
- package/templates/contract/validation.md +1 -1
|
@@ -1,8 +1,49 @@
|
|
|
1
1
|
import { cssIdent } from './security.js';
|
|
2
2
|
import { html, nothing, svg } from './templates.js';
|
|
3
|
+
import { splitGraduationHistory } from './view-parts.js';
|
|
3
4
|
|
|
4
5
|
const clip = (s, n) => (s.length > n ? `${s.slice(0, n - 1)}…` : s);
|
|
5
6
|
|
|
7
|
+
// Plain-text excerpt for spec cards: strips the leading graduation-history
|
|
8
|
+
// blockquote, picks the first prose paragraph (skipping headings, remaining
|
|
9
|
+
// blockquotes and code fences) and removes inline Markdown syntax. The result
|
|
10
|
+
// is interpolated as text (lit-html), never as HTML.
|
|
11
|
+
function firstProseParagraph(text) {
|
|
12
|
+
const paragraphs = String(text ?? '').split(/\n\s*\n/);
|
|
13
|
+
for (const para of paragraphs) {
|
|
14
|
+
const trimmed = para.trim();
|
|
15
|
+
if (!trimmed) continue;
|
|
16
|
+
if (/^#{1,6}\s/.test(trimmed)) continue;
|
|
17
|
+
if (/^>/.test(trimmed)) continue;
|
|
18
|
+
if (/^```/.test(trimmed)) continue;
|
|
19
|
+
return trimmed;
|
|
20
|
+
}
|
|
21
|
+
return '';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function stripMarkdown(text) {
|
|
25
|
+
return text
|
|
26
|
+
.replace(/`([^`]*)`/g, '$1')
|
|
27
|
+
.replace(/\*\*([^*]+)\*\*/g, '$1')
|
|
28
|
+
.replace(/__([^_]+)__/g, '$1')
|
|
29
|
+
.replace(/\*([^*]+)\*/g, '$1')
|
|
30
|
+
.replace(/_([^_]+)_/g, '$1')
|
|
31
|
+
.replace(/\[([^\]]*)\]\([^)]*\)/g, '$1')
|
|
32
|
+
.replace(/\s+/g, ' ')
|
|
33
|
+
.trim();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function specExcerpt(body, maxLen = 160) {
|
|
37
|
+
const { after } = splitGraduationHistory(body);
|
|
38
|
+
return clip(stripMarkdown(firstProseParagraph(after)), maxLen);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Most recently updated truth first.
|
|
42
|
+
export function sortSpecsByUpdated(specs) {
|
|
43
|
+
const time = (s) => Date.parse(s?.updated || '') || 0;
|
|
44
|
+
return [...specs].sort((a, b) => time(b) - time(a));
|
|
45
|
+
}
|
|
46
|
+
|
|
6
47
|
export function graphSvg(changes) {
|
|
7
48
|
if (!changes.length) {
|
|
8
49
|
return html`<p class="empty">No changes match the current filters.</p>`;
|
|
@@ -79,17 +120,19 @@ export function graphSvg(changes) {
|
|
|
79
120
|
}
|
|
80
121
|
|
|
81
122
|
export function specsListHtml(specs, fmtDateTime) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
</
|
|
91
|
-
|
|
92
|
-
|
|
123
|
+
if (!specs.length) {
|
|
124
|
+
return html`<p class="empty">No specs yet. Truth graduates here as changes complete.</p>`;
|
|
125
|
+
}
|
|
126
|
+
return sortSpecsByUpdated(specs).map(
|
|
127
|
+
(s, i) => html`<div class="spec-card" data-i=${i}>
|
|
128
|
+
<div class="spec-title">${s.title}</div>
|
|
129
|
+
<div class="card-meta">
|
|
130
|
+
<span title=${s.updated || ''}>${fmtDateTime(s.updated)}</span>
|
|
131
|
+
${(s.tags || []).map((t) => html`<span class="pill">${t}</span>`)}
|
|
132
|
+
</div>
|
|
133
|
+
<p class="spec-excerpt">${specExcerpt(s.body)}</p>
|
|
134
|
+
</div>`,
|
|
135
|
+
);
|
|
93
136
|
}
|
|
94
137
|
|
|
95
138
|
export function fmtDuration(ms) {
|
|
@@ -111,15 +154,86 @@ function barRows(items, label, value, fmt = (v) => v) {
|
|
|
111
154
|
);
|
|
112
155
|
}
|
|
113
156
|
|
|
114
|
-
|
|
157
|
+
// Hand-rolled SVG bar chart for throughput: one bar per day, a date label and
|
|
158
|
+
// the numeric count both drawn as text so the value is visible without a
|
|
159
|
+
// tooltip (no charting dependency — same precedent as `graphSvg`).
|
|
160
|
+
function throughputSvg(tp) {
|
|
161
|
+
if (!tp.length) return html`<p class="empty">No closed changes yet.</p>`;
|
|
162
|
+
|
|
163
|
+
const width = 640;
|
|
164
|
+
const height = 180;
|
|
165
|
+
const padTop = 22;
|
|
166
|
+
const padBottom = 30;
|
|
167
|
+
const padSide = 12;
|
|
168
|
+
const plotWidth = Math.max(1, width - padSide * 2);
|
|
169
|
+
const plotHeight = Math.max(1, height - padTop - padBottom);
|
|
170
|
+
const max = Math.max(1, ...tp.map((t) => t.count));
|
|
171
|
+
const slot = plotWidth / tp.length;
|
|
172
|
+
const barWidth = Math.max(2, Math.min(48, slot - 8));
|
|
173
|
+
|
|
174
|
+
const bars = tp.map((t, i) => {
|
|
175
|
+
const barHeight = (t.count / max) * plotHeight;
|
|
176
|
+
const x = padSide + i * slot + (slot - barWidth) / 2;
|
|
177
|
+
const y = height - padBottom - barHeight;
|
|
178
|
+
return svg`<g class="tp-bar">
|
|
179
|
+
<rect class="tp-rect" x=${x} y=${y} width=${barWidth} height=${barHeight}></rect>
|
|
180
|
+
<text class="tp-value" x=${x + barWidth / 2} y=${Math.max(12, y - 6)} text-anchor="middle">${t.count}</text>
|
|
181
|
+
<text class="tp-date" x=${x + barWidth / 2} y=${height - padBottom + 16} text-anchor="middle">${t.date}</text>
|
|
182
|
+
</g>`;
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
return html`<svg
|
|
186
|
+
class="throughput-svg"
|
|
187
|
+
viewBox=${`0 0 ${width} ${height}`}
|
|
188
|
+
height=${height}
|
|
189
|
+
role="img"
|
|
190
|
+
aria-label="Throughput per day"
|
|
191
|
+
>
|
|
192
|
+
<line
|
|
193
|
+
class="tp-axis"
|
|
194
|
+
x1=${padSide}
|
|
195
|
+
y1=${height - padBottom}
|
|
196
|
+
x2=${width - padSide}
|
|
197
|
+
y2=${height - padBottom}
|
|
198
|
+
></line>
|
|
199
|
+
${bars}
|
|
200
|
+
</svg>`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function metricsTable(rows, columnLabel, labelOf) {
|
|
204
|
+
return rows.length
|
|
205
|
+
? html`<table class="grid">
|
|
206
|
+
<thead>
|
|
207
|
+
<tr><th>${columnLabel}</th><th>Closed</th><th>Avg cycle</th></tr>
|
|
208
|
+
</thead>
|
|
209
|
+
<tbody>
|
|
210
|
+
${rows.map(
|
|
211
|
+
(r) => html`<tr>
|
|
212
|
+
<td>${labelOf(r)}</td>
|
|
213
|
+
<td class="mono">${r.closed}</td>
|
|
214
|
+
<td class="mono">${fmtDuration(r.avgCycleMs)}</td>
|
|
215
|
+
</tr>`,
|
|
216
|
+
)}
|
|
217
|
+
</tbody>
|
|
218
|
+
</table>`
|
|
219
|
+
: html`<p class="empty">No closed changes yet.</p>`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function metricsHtml(metrics = {}, totalChanges = 0) {
|
|
223
|
+
if (!totalChanges) {
|
|
224
|
+
return html`<p class="empty">No changes match the current filters.</p>`;
|
|
225
|
+
}
|
|
226
|
+
|
|
115
227
|
const wip = metrics.wip || {};
|
|
116
228
|
const wipTotal = Object.values(wip).reduce((a, b) => a + b, 0);
|
|
117
229
|
const cards = [
|
|
118
230
|
['Closed', metrics.count ?? 0],
|
|
119
|
-
['
|
|
120
|
-
['
|
|
231
|
+
['Cycle p50', fmtDuration(metrics.p50CycleMs)],
|
|
232
|
+
['Cycle p85', fmtDuration(metrics.p85CycleMs)],
|
|
121
233
|
['WIP', wipTotal],
|
|
122
234
|
['Blocked time', fmtDuration(metrics.blockedMs)],
|
|
235
|
+
['Validation wait', fmtDuration(metrics.validationWaitMs)],
|
|
236
|
+
['Review retries', metrics.reviewRetries ?? 0],
|
|
123
237
|
].map(
|
|
124
238
|
([label, val]) =>
|
|
125
239
|
html`<div class="metric-card">
|
|
@@ -140,15 +254,6 @@ export function metricsHtml(metrics = {}) {
|
|
|
140
254
|
)
|
|
141
255
|
: html`<p class="empty">No data yet.</p>`;
|
|
142
256
|
|
|
143
|
-
const tp = metrics.throughput || [];
|
|
144
|
-
const tpBars = tp.length
|
|
145
|
-
? barRows(
|
|
146
|
-
tp,
|
|
147
|
-
(t) => html`<span class="mono">${t.date}</span>`,
|
|
148
|
-
(t) => t.count,
|
|
149
|
-
)
|
|
150
|
-
: html`<p class="empty">No closed changes yet.</p>`;
|
|
151
|
-
|
|
152
257
|
const aging = metrics.aging || [];
|
|
153
258
|
const agingRows = aging.length
|
|
154
259
|
? html`<ul class="git-commits">
|
|
@@ -160,32 +265,39 @@ export function metricsHtml(metrics = {}) {
|
|
|
160
265
|
: html`<p class="empty">Nothing in progress.</p>`;
|
|
161
266
|
|
|
162
267
|
const byType = metrics.byType || [];
|
|
163
|
-
const typeRows =
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
</tr>`,
|
|
175
|
-
)}
|
|
176
|
-
</tbody>
|
|
177
|
-
</table>`
|
|
178
|
-
: html`<p class="empty">No closed changes yet.</p>`;
|
|
268
|
+
const typeRows = metricsTable(
|
|
269
|
+
byType,
|
|
270
|
+
'Type',
|
|
271
|
+
(t) =>
|
|
272
|
+
html`<span class="type-tag" style=${`--type-color: var(--${cssIdent(t.type)})`}>${t.type}</span>`,
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
const byOwner = metrics.byOwner || [];
|
|
276
|
+
const ownerRows = metricsTable(byOwner, 'Owner', (o) =>
|
|
277
|
+
o.owner === 'unassigned' ? html`<span class="muted">Unassigned</span>` : o.owner,
|
|
278
|
+
);
|
|
179
279
|
|
|
180
280
|
return html`
|
|
181
281
|
<div class="metrics-cards">${cards}</div>
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
282
|
+
<div class="metrics-grid">
|
|
283
|
+
<section class="metrics-panel">
|
|
284
|
+
<h3 class="metrics-h">Throughput (closed per day)</h3>
|
|
285
|
+
${throughputSvg(metrics.throughput || [])}
|
|
286
|
+
</section>
|
|
287
|
+
<section class="metrics-panel">
|
|
288
|
+
<h3 class="metrics-h">Avg time in status (lead time per stage)</h3>
|
|
289
|
+
<div>${leadBars}</div>
|
|
290
|
+
</section>
|
|
291
|
+
<section class="metrics-panel">
|
|
292
|
+
<h3 class="metrics-h">Aging — in progress</h3>
|
|
293
|
+
${wipChips.length ? html`<div class="detail-meta">${wipChips}</div>` : nothing}
|
|
294
|
+
${agingRows}
|
|
295
|
+
</section>
|
|
296
|
+
<section class="metrics-panel">
|
|
297
|
+
<h3 class="metrics-h">By type</h3>
|
|
298
|
+
${typeRows}
|
|
299
|
+
<h3 class="metrics-h">By owner</h3>
|
|
300
|
+
${ownerRows}
|
|
301
|
+
</section>
|
|
302
|
+
</div>`;
|
|
191
303
|
}
|
|
@@ -2,7 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { gitRefs } from '../../git.mjs';
|
|
5
|
-
import { publicDir } from '../../paths.mjs';
|
|
5
|
+
import { packageRoot, publicDir } from '../../paths.mjs';
|
|
6
6
|
import { loadRepoAsync } from '../../repo.mjs';
|
|
7
7
|
import {
|
|
8
8
|
applyConfigMigration,
|
|
@@ -49,10 +49,21 @@ function vendorFile(route) {
|
|
|
49
49
|
const MIME = {
|
|
50
50
|
'.html': 'text/html; charset=utf-8',
|
|
51
51
|
'.js': 'text/javascript; charset=utf-8',
|
|
52
|
+
'.mjs': 'text/javascript; charset=utf-8',
|
|
52
53
|
'.css': 'text/css; charset=utf-8',
|
|
53
54
|
'.json': 'application/json; charset=utf-8',
|
|
54
55
|
};
|
|
55
56
|
|
|
57
|
+
// The pure metrics module is shared verbatim between the CLI and the browser:
|
|
58
|
+
// the client dynamic-imports it so filtered metrics reuse the exact same
|
|
59
|
+
// computation as the server payload (no reimplementation). `lifecycle.mjs` is
|
|
60
|
+
// its only relative import, so both must be reachable under the same route
|
|
61
|
+
// prefix for the browser's module resolution to find it. Neither file is
|
|
62
|
+
// under `publicDir`; this is a narrow, explicit allowlist rather than opening
|
|
63
|
+
// up the rest of `src/` the way `publicDir` static assets are.
|
|
64
|
+
const SHARED_MODULES_DIR = path.join(packageRoot, 'src');
|
|
65
|
+
const SHARED_MODULES = new Set(['metrics.mjs', 'lifecycle.mjs']);
|
|
66
|
+
|
|
56
67
|
// Defensive headers for a local-only UI: never sniff types, never cache, and
|
|
57
68
|
// forbid embedding in a frame (clickjacking).
|
|
58
69
|
const SECURITY_HEADERS = {
|
|
@@ -215,6 +226,13 @@ export function createRequestListener(cwd, localOnly, token) {
|
|
|
215
226
|
return;
|
|
216
227
|
}
|
|
217
228
|
|
|
229
|
+
if (route.startsWith('/shared/')) {
|
|
230
|
+
const shared = sharedModuleFile(route);
|
|
231
|
+
if (shared) send(res, 200, MIME['.mjs'], fs.readFileSync(shared));
|
|
232
|
+
else send(res, 404, 'text/plain', 'Not found');
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
218
236
|
const vendor = vendorFile(route);
|
|
219
237
|
if (vendor) {
|
|
220
238
|
if (fs.existsSync(vendor)) send(res, 200, MIME['.js'], fs.readFileSync(vendor));
|
|
@@ -239,7 +257,10 @@ export function createRequestListener(cwd, localOnly, token) {
|
|
|
239
257
|
};
|
|
240
258
|
}
|
|
241
259
|
|
|
242
|
-
|
|
260
|
+
// Resolves `route` to a file under `root`, refusing anything that decodes,
|
|
261
|
+
// resolves, or (post-symlink) realpaths outside of it. Shared by the public
|
|
262
|
+
// static assets and the narrower shared-module allowlist below.
|
|
263
|
+
function assetFile(root, route) {
|
|
243
264
|
let decoded;
|
|
244
265
|
try {
|
|
245
266
|
decoded = decodeURIComponent(route);
|
|
@@ -249,16 +270,29 @@ export function staticFile(route) {
|
|
|
249
270
|
if (decoded.includes('\0')) return null;
|
|
250
271
|
|
|
251
272
|
const rel = decoded.replace(/^\/+/, '');
|
|
252
|
-
const file = path.resolve(
|
|
253
|
-
if (!isInsidePath(
|
|
273
|
+
const file = path.resolve(root, rel);
|
|
274
|
+
if (!isInsidePath(root, file)) return null;
|
|
254
275
|
if (!fs.existsSync(file) || !fs.statSync(file).isFile()) return null;
|
|
255
276
|
|
|
256
|
-
const
|
|
277
|
+
const realRoot = fs.realpathSync(root);
|
|
257
278
|
const realFile = fs.realpathSync(file);
|
|
258
|
-
if (!isInsidePath(
|
|
279
|
+
if (!isInsidePath(realRoot, realFile)) return null;
|
|
259
280
|
return file;
|
|
260
281
|
}
|
|
261
282
|
|
|
283
|
+
export function staticFile(route) {
|
|
284
|
+
return assetFile(publicDir, route);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Serves only the whitelisted shared modules — never arbitrary files under
|
|
288
|
+
// `src/` — even though containment is checked the same way as public assets.
|
|
289
|
+
export function sharedModuleFile(route) {
|
|
290
|
+
if (!route.startsWith('/shared/')) return null;
|
|
291
|
+
const name = route.slice('/shared/'.length);
|
|
292
|
+
if (!SHARED_MODULES.has(name)) return null;
|
|
293
|
+
return assetFile(SHARED_MODULES_DIR, `/${name}`);
|
|
294
|
+
}
|
|
295
|
+
|
|
262
296
|
function isInsidePath(root, target) {
|
|
263
297
|
const rel = path.relative(path.resolve(root), path.resolve(target));
|
|
264
298
|
return rel === '' || (rel && !rel.startsWith('..') && !path.isAbsolute(rel));
|
package/templates/config.yml
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
# Schema version — used to detect and migrate outdated configurations.
|
|
5
5
|
# Run `changeledger config migrate --dry-run` to inspect available migrations.
|
|
6
|
-
schema_version:
|
|
6
|
+
schema_version: 2
|
|
7
7
|
|
|
8
8
|
# Language for generated documentation CONTENT. Structure (headings, keys, enums)
|
|
9
9
|
# is always English. See `changeledger context spec`.
|
|
@@ -23,6 +23,7 @@ release:
|
|
|
23
23
|
audit: none
|
|
24
24
|
refactor: none
|
|
25
25
|
chore: none
|
|
26
|
+
quick: patch
|
|
26
27
|
|
|
27
28
|
# Optional Definition of Ready path/command hints. When present, tasks that
|
|
28
29
|
# reference CRs should name at least one target and one verification matching
|
|
@@ -58,3 +59,8 @@ types:
|
|
|
58
59
|
review_required: true
|
|
59
60
|
chore:
|
|
60
61
|
stages: [request, plan]
|
|
62
|
+
# Small, reversible, single-concern work with no persistent-truth or public
|
|
63
|
+
# surface impact. If scope grows mid-execution, discard and recreate under
|
|
64
|
+
# the correct type — see `changeledger context spec`.
|
|
65
|
+
quick:
|
|
66
|
+
stages: [request, log]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Read-Only Audit Delegate
|
|
2
|
+
|
|
3
|
+
This is a self-contained delegated context. It replaces the ChangeLedger core
|
|
4
|
+
for this role; do not run `changeledger context` or load another ChangeLedger
|
|
5
|
+
context.
|
|
6
|
+
|
|
7
|
+
This is a post-review inspection of a change already sitting in
|
|
8
|
+
`in-validation`, waiting for human acceptance. The review gate already ran;
|
|
9
|
+
you do not repeat it and you do not move the change. This role is read-only:
|
|
10
|
+
do not modify files, do not change Git state, do not mutate the ledger, do not
|
|
11
|
+
change status, do not add Log entries, and do not delegate any part of the
|
|
12
|
+
work.
|
|
13
|
+
|
|
14
|
+
Inspect the selected change, every `CRn`, the actual diff, tests and Git
|
|
15
|
+
history against what the document claims. Contrast criteria, code and
|
|
16
|
+
evidence; note any drift, residue or unresolved risk.
|
|
17
|
+
|
|
18
|
+
Return findings and evidence only — file:line references, what you confirmed,
|
|
19
|
+
what you could not confirm. Do not decide or state whether the change should
|
|
20
|
+
be accepted, sent back or blocked, and do not name or suggest a lifecycle
|
|
21
|
+
command: that decision belongs to the human waiting at `in-validation`, not to
|
|
22
|
+
this role.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Implementation Delegate
|
|
2
|
+
|
|
3
|
+
This is a self-contained delegated context. It replaces the ChangeLedger core
|
|
4
|
+
for this role; do not run `changeledger context` or load another ChangeLedger
|
|
5
|
+
context.
|
|
6
|
+
|
|
7
|
+
Implement only the bounded assignment in the delegation prompt and follow the
|
|
8
|
+
selected change's Specification and Plan exactly. With `tdd=on`, write the
|
|
9
|
+
failing test for each assigned criterion, make it pass, then refactor.
|
|
10
|
+
|
|
11
|
+
Modify only the files assigned in the delegation prompt. You share the worktree:
|
|
12
|
+
do not revert or overwrite others' work; stop and report any overlap instead of
|
|
13
|
+
resolving it silently. Do not change Git state, mutate the ledger or delegate
|
|
14
|
+
any part of the work. Lifecycle, tasks, Log, review and integration remain the
|
|
15
|
+
orchestrator's responsibility.
|
|
16
|
+
|
|
17
|
+
Return the files changed, criteria covered, tests run and their results, plus
|
|
18
|
+
any overlap or uncertainty. The orchestrator integrates and records the work.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Investigation Delegate
|
|
2
|
+
|
|
3
|
+
This is a self-contained delegated context. It replaces the ChangeLedger core
|
|
4
|
+
for this role; do not run `changeledger context` or load another ChangeLedger
|
|
5
|
+
context.
|
|
6
|
+
|
|
7
|
+
Answer only the investigation question and ownership boundary in the delegation
|
|
8
|
+
prompt. This role is read-only: do not modify files, Git state or the ledger, and
|
|
9
|
+
do not delegate any part of the work.
|
|
10
|
+
|
|
11
|
+
Inspect the smallest useful surface. Return concrete findings and evidence such
|
|
12
|
+
as file:line references, constraints, risks and a root-cause statement when
|
|
13
|
+
applicable. State what could not be determined. Do not implement fixes or create
|
|
14
|
+
ChangeLedger artifacts.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Independent Review Delegate
|
|
2
|
+
|
|
3
|
+
This is a self-contained delegated context. It replaces the ChangeLedger core
|
|
4
|
+
for this role; do not run `changeledger context` or load another ChangeLedger
|
|
5
|
+
context.
|
|
6
|
+
|
|
7
|
+
Review with clean context and do not trust the implementer's summary. This role
|
|
8
|
+
is read-only: do not modify files, Git state or the ledger, do not record the
|
|
9
|
+
verdict, and do not delegate any part of the work.
|
|
10
|
+
|
|
11
|
+
Inspect the selected change, every `CRn`, every Plan task, tests, the actual diff
|
|
12
|
+
and absence of TODO/FIXME, dead code or unrelated residue. Confirm tasks are
|
|
13
|
+
true rather than merely checked off and that implementation did not drift from
|
|
14
|
+
the authorized document.
|
|
15
|
+
|
|
16
|
+
Deep security, SAST and lint belong to dedicated tools. You may run them
|
|
17
|
+
read-only and report their evidence; ChangeLedger does not reimplement them.
|
|
18
|
+
|
|
19
|
+
Return per-criterion PASS/FAIL evidence and one recommended outcome: pass,
|
|
20
|
+
fail-retry with a reason for a fixable defect inside scope, or fail-block with a
|
|
21
|
+
reason when correction needs scope or product judgment. The orchestrator alone
|
|
22
|
+
records the verdict.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Delegation skeleton — role: audit
|
|
2
|
+
|
|
3
|
+
Fill every `{{placeholder}}` before handing this prompt to the subagent. Delete
|
|
4
|
+
guidance in parentheses. The auditor is a read-only inspection of a change
|
|
5
|
+
already sitting in `in-validation`, waiting for human acceptance; it is not a
|
|
6
|
+
review and never moves the change.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
You are a READ-ONLY AUDIT delegate. Do not delegate any part of this to
|
|
11
|
+
another agent; execute it yourself.
|
|
12
|
+
|
|
13
|
+
Why this is delegated: {{reason}} (independent inspection after review already
|
|
14
|
+
passed and the change is waiting for a human at `in-validation`).
|
|
15
|
+
|
|
16
|
+
Your prompt identifies you as a ChangeLedger delegate. As your only
|
|
17
|
+
ChangeLedger load, run `changeledger agent-context audit {{change_id}}` and
|
|
18
|
+
read it through its END sentinel; do not load the orchestrator core.
|
|
19
|
+
|
|
20
|
+
Change under audit: {{change_id}}.
|
|
21
|
+
|
|
22
|
+
Boundaries — expressed by effect, not by tool name: do not modify any file, do
|
|
23
|
+
not change Git state, and do not mutate the ledger. You inspect and report
|
|
24
|
+
only; the review gate already ran, so do not issue a verdict and do not name
|
|
25
|
+
or suggest a lifecycle command.
|
|
26
|
+
|
|
27
|
+
Expected output: {{expected_output}} (findings and evidence — file:line
|
|
28
|
+
references, what was confirmed, what could not be, and any drift or residue).
|
|
29
|
+
|
|
30
|
+
Difficulty or risk that set the model choice: {{difficulty_or_risk}}.
|
|
31
|
+
|
|
32
|
+
Return to the orchestrator or the human waiting at `in-validation` the
|
|
33
|
+
findings and evidence above; you never move the change.
|
|
34
|
+
|
|
35
|
+
Integration criterion: {{integration}} (how the orchestrator or human uses the
|
|
36
|
+
findings, e.g. it informs the human's accept/reject decision at
|
|
37
|
+
`in-validation`).
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Delegation skeleton — role: implementation
|
|
2
|
+
|
|
3
|
+
Fill every `{{placeholder}}` before handing this prompt to the subagent. Delete
|
|
4
|
+
guidance in parentheses. The delegate writes code within a bounded ownership; the
|
|
5
|
+
orchestrator keeps the ledger and integration.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are an IMPLEMENTATION delegate. Do not delegate any part of this to another
|
|
10
|
+
agent; execute it yourself.
|
|
11
|
+
|
|
12
|
+
Why this is delegated: {{reason}} (why a separate agent, e.g. a disjoint write
|
|
13
|
+
set that parallelizes safely, a sufficient cheaper model for well-specified
|
|
14
|
+
execution).
|
|
15
|
+
|
|
16
|
+
Your prompt identifies you as a ChangeLedger delegate. As your only ChangeLedger
|
|
17
|
+
load, run `changeledger agent-context implementation {{change_id}}` and read it
|
|
18
|
+
through its END sentinel; do not load the orchestrator core. It supplies the
|
|
19
|
+
selected change with its acceptance criteria and Plan.
|
|
20
|
+
|
|
21
|
+
If that command does not resolve the change, or the working tree does not
|
|
22
|
+
contain its document, stop and report instead of proceeding: never reconstruct
|
|
23
|
+
the change from memory, and never continue from another base.
|
|
24
|
+
|
|
25
|
+
Files you own: {{files}} (the only paths you may modify).
|
|
26
|
+
|
|
27
|
+
Boundaries — expressed by effect, not by tool name: modify only the files under
|
|
28
|
+
your ownership above; do not revert or overwrite anyone else's work; if you find
|
|
29
|
+
your change overlaps another change's surface, stop and report it instead of
|
|
30
|
+
resolving it silently. Do not mutate the ledger — status, task, log, review and
|
|
31
|
+
graduation transitions are the orchestrator's; you implement and report.
|
|
32
|
+
|
|
33
|
+
Expected output: {{expected_output}} (the code plus the tests that prove the
|
|
34
|
+
cited criteria, red-green; state which criteria you covered).
|
|
35
|
+
|
|
36
|
+
Difficulty or risk that set the model choice: {{difficulty_or_risk}}.
|
|
37
|
+
|
|
38
|
+
Return to the orchestrator only what changed and how it was verified — the files
|
|
39
|
+
touched, the tests run and their result, and any overlap you hit.
|
|
40
|
+
|
|
41
|
+
Integration criterion: {{integration}} (how the orchestrator merges/verifies your
|
|
42
|
+
work, e.g. it re-runs the full gate and records the ledger transitions).
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Delegation skeleton — role: investigation
|
|
2
|
+
|
|
3
|
+
Fill every `{{placeholder}}` before handing this prompt to the subagent. Delete
|
|
4
|
+
guidance in parentheses. This is a read-only inquiry: the delegate answers a
|
|
5
|
+
question, it does not change anything.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are an INVESTIGATION delegate. Do not delegate any part of this to another
|
|
10
|
+
agent; execute it yourself.
|
|
11
|
+
|
|
12
|
+
Why this is delegated: {{reason}} (why a separate agent, e.g. protect main
|
|
13
|
+
context, parallelize an independent question, bring a stronger model to hard
|
|
14
|
+
analysis).
|
|
15
|
+
|
|
16
|
+
Question you own: {{question}} (the single question or area to investigate;
|
|
17
|
+
state it concretely).
|
|
18
|
+
|
|
19
|
+
Your prompt identifies you as a ChangeLedger delegate. As your only ChangeLedger
|
|
20
|
+
load, run `changeledger agent-context investigation {{change_id}}` and read it
|
|
21
|
+
through its END sentinel; do not load the orchestrator core. There may be no
|
|
22
|
+
change yet: work without a change id. If the optional id below is empty, omit it
|
|
23
|
+
from the command.
|
|
24
|
+
|
|
25
|
+
Optional selected change: {{change_id}} (leave empty when investigating before a
|
|
26
|
+
change exists).
|
|
27
|
+
|
|
28
|
+
Boundaries — expressed by effect, not by tool name: do not modify any file, do
|
|
29
|
+
not change Git state, and do not mutate the ledger (no status, task, log, review
|
|
30
|
+
or graduation). Inspect and read only.
|
|
31
|
+
|
|
32
|
+
Expected output: {{expected_output}} (what the answer must contain, e.g. a
|
|
33
|
+
file:line map, a root-cause statement, a constraints/risks list).
|
|
34
|
+
|
|
35
|
+
Difficulty or risk that set the model choice: {{difficulty_or_risk}}.
|
|
36
|
+
|
|
37
|
+
Return to the orchestrator only findings/data — no narrative, no fixes. State
|
|
38
|
+
clearly what you could not determine.
|
|
39
|
+
|
|
40
|
+
Integration criterion: {{integration}} (how the orchestrator will use the answer,
|
|
41
|
+
e.g. it feeds the Investigation stage of the change).
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Delegation skeleton — role: review
|
|
2
|
+
|
|
3
|
+
Fill every `{{placeholder}}` before handing this prompt to the subagent. Delete
|
|
4
|
+
guidance in parentheses. The reviewer is a fresh, clean-context, read-only check;
|
|
5
|
+
the orchestrator alone records the verdict.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are an INDEPENDENT REVIEW delegate with clean context. Do not delegate any
|
|
10
|
+
part of this to another agent; execute it yourself.
|
|
11
|
+
|
|
12
|
+
Why this is delegated: {{reason}} (independence is a correctness requirement, not
|
|
13
|
+
an optimization — a fresh reviewer that does not trust the implementer's summary).
|
|
14
|
+
|
|
15
|
+
Your prompt identifies you as a ChangeLedger delegate. As your only ChangeLedger
|
|
16
|
+
load, run `changeledger agent-context review {{change_id}}` and read it through
|
|
17
|
+
its END sentinel; do not load the orchestrator core. Use the inspection
|
|
18
|
+
checklist that agent-context gives you.
|
|
19
|
+
|
|
20
|
+
Change under review: {{change_id}}.
|
|
21
|
+
|
|
22
|
+
Boundaries — expressed by effect, not by tool name: do not modify any file, do
|
|
23
|
+
not change Git state, and do not mutate the ledger. You inspect and report only;
|
|
24
|
+
you never record the verdict — the orchestrator does that.
|
|
25
|
+
|
|
26
|
+
Expected output: {{expected_output}} (per-criterion PASS/FAIL with concrete
|
|
27
|
+
evidence, plus any drift or residue found).
|
|
28
|
+
|
|
29
|
+
Difficulty or risk that set the model choice: {{difficulty_or_risk}}.
|
|
30
|
+
|
|
31
|
+
Return to the orchestrator a single recommended verdict — one of pass, fail-retry
|
|
32
|
+
with a reason, or fail-block with a reason — with the evidence behind it. The
|
|
33
|
+
orchestrator records it.
|
|
34
|
+
|
|
35
|
+
Integration criterion: {{integration}} (how the orchestrator acts on the verdict,
|
|
36
|
+
e.g. it records pass and moves the change to in-validation).
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"base": {
|
|
3
|
+
"core": { "target": { "lines": 125, "bytes": 7500 }, "hard": { "lines": 140, "bytes": 9000 } },
|
|
4
|
+
"spec": { "target": { "lines": 280, "bytes": 12000 }, "hard": { "lines": 310, "bytes": 13500 } },
|
|
5
|
+
"implement": { "target": { "lines": 185, "bytes": 8500 }, "hard": { "lines": 205, "bytes": 10000 } },
|
|
6
|
+
"review": { "target": { "lines": 70, "bytes": 3500 }, "hard": { "lines": 85, "bytes": 4500 } },
|
|
7
|
+
"release": { "target": { "lines": 45, "bytes": 2500 }, "hard": { "lines": 60, "bytes": 3500 } }
|
|
8
|
+
},
|
|
9
|
+
"agent": { "target": { "lines": 45, "bytes": 2000 }, "hard": { "lines": 60, "bytes": 3000 } }
|
|
10
|
+
,"overlays": {
|
|
11
|
+
"blocked": { "target": { "lines": 70, "bytes": 3000 }, "hard": { "lines": 84, "bytes": 3600 } },
|
|
12
|
+
"in-validation": { "target": { "lines": 45, "bytes": 1700 }, "hard": { "lines": 54, "bytes": 2040 } },
|
|
13
|
+
"done": { "target": { "lines": 90, "bytes": 3500 }, "hard": { "lines": 108, "bytes": 4200 } },
|
|
14
|
+
"discarded": { "target": { "lines": 40, "bytes": 1300 }, "hard": { "lines": 48, "bytes": 1560 } }
|
|
15
|
+
}
|
|
16
|
+
}
|