mandrel 2.35.0 → 2.36.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/docs/agentrc-reference.json +3 -1
- package/.agents/docs/configuration.md +2 -0
- package/.agents/schemas/agentrc.schema.json +11 -0
- package/.agents/schemas/lifecycle/merge.unlanded.schema.json +2 -1
- package/.agents/schemas/story-deliver-terminal.schema.json +1 -0
- package/.agents/scripts/check-doc-links.js +23 -2
- package/.agents/scripts/git-cleanup.js +2 -0
- package/.agents/scripts/lib/config/ci.js +18 -0
- package/.agents/scripts/lib/config-settings-schema-delivery.js +13 -0
- package/.agents/scripts/lib/observability/source-classifier.js +0 -1
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/branches.js +22 -7
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/git-probes.js +22 -14
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/merged-tip.js +132 -0
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/render.js +56 -11
- package/.agents/scripts/lib/orchestration/merge-block-class.js +10 -1
- package/.agents/scripts/lib/orchestration/merge-poll.js +164 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +145 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +96 -5
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +9 -1
- package/.agents/scripts/notify.js +4 -10
- package/.agents/workflows/audit-documentation.md +5 -6
- package/docs/CHANGELOG.md +18 -0
- package/package.json +3 -3
- package/.agents/scripts/generate-lifecycle-docs.js +0 -237
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* .agents/scripts/generate-lifecycle-docs.js — Schema-backed lifecycle docs table
|
|
4
|
-
*
|
|
5
|
-
* Renders a bounded region inside `docs/LIFECYCLE.md` from every
|
|
6
|
-
* `.agents/schemas/lifecycle/*.schema.json`. The region is delimited by:
|
|
7
|
-
*
|
|
8
|
-
* <!-- BEGIN GENERATED:lifecycle-events -->
|
|
9
|
-
* ...generated table...
|
|
10
|
-
* <!-- END GENERATED:lifecycle-events -->
|
|
11
|
-
*
|
|
12
|
-
* Columns: | Event | Schema | Description | Required fields |
|
|
13
|
-
*
|
|
14
|
-
* - Event = schema filename minus `.schema.json`
|
|
15
|
-
* - Schema = relative markdown link to the schema file
|
|
16
|
-
* - Description = the schema's top-level `description` property
|
|
17
|
-
* - Required fields = comma-joined list from the schema's `required` array
|
|
18
|
-
* (rendered as inline code; "—" when empty/absent)
|
|
19
|
-
*
|
|
20
|
-
* Skips `README.md`. The `ledger-record.schema.json` file is a record
|
|
21
|
-
* envelope rather than a lifecycle event, but to keep this generator
|
|
22
|
-
* literal-schema driven we still emit a row for it — the surrounding doc
|
|
23
|
-
* already calls out the distinction.
|
|
24
|
-
*
|
|
25
|
-
* Modes:
|
|
26
|
-
* (default) — rewrites the bounded region in place.
|
|
27
|
-
* --check — exits 0 when the on-disk region matches the freshly
|
|
28
|
-
* generated content, exits 1 with a diff hint otherwise.
|
|
29
|
-
*
|
|
30
|
-
* Per `.agents/rules/orchestration-error-handling.md`, unrecoverable
|
|
31
|
-
* failures surface via `throw new Error(...)` so `runAsCli` can map the
|
|
32
|
-
* throw to `process.exit(1)` deterministically (no `Logger.fatal`).
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
|
-
import fs from 'node:fs';
|
|
36
|
-
import path from 'node:path';
|
|
37
|
-
import { fileURLToPath } from 'node:url';
|
|
38
|
-
import { parseArgs } from 'node:util';
|
|
39
|
-
import { runAsCli } from './lib/cli-utils.js';
|
|
40
|
-
import { Logger } from './lib/Logger.js';
|
|
41
|
-
|
|
42
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
43
|
-
const __dirname = path.dirname(__filename);
|
|
44
|
-
const PROJECT_ROOT = path.resolve(__dirname, '..', '..');
|
|
45
|
-
const SCHEMA_DIR = path.join(PROJECT_ROOT, '.agents', 'schemas', 'lifecycle');
|
|
46
|
-
const DOC_PATH = path.join(PROJECT_ROOT, 'docs', 'LIFECYCLE.md');
|
|
47
|
-
const REGION_BEGIN = '<!-- BEGIN GENERATED:lifecycle-events -->';
|
|
48
|
-
const REGION_END = '<!-- END GENERATED:lifecycle-events -->';
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Read and parse every `*.schema.json` under the lifecycle schema dir, in
|
|
52
|
-
* ASCII-sorted filename order. The sort is intentional — it produces a
|
|
53
|
-
* stable diff regardless of the host filesystem's enumeration order, which
|
|
54
|
-
* is what makes `--check` reliable across platforms.
|
|
55
|
-
*
|
|
56
|
-
* @param {string} dir Absolute path to the schema directory.
|
|
57
|
-
* @returns {Array<{event:string, file:string, description:string, required:string[]}>}
|
|
58
|
-
*/
|
|
59
|
-
function readLifecycleSchemas(dir) {
|
|
60
|
-
if (!fs.existsSync(dir)) {
|
|
61
|
-
throw new Error(`Lifecycle schema directory not found: ${dir}`);
|
|
62
|
-
}
|
|
63
|
-
const entries = fs
|
|
64
|
-
.readdirSync(dir)
|
|
65
|
-
.filter((name) => name.endsWith('.schema.json'))
|
|
66
|
-
.sort();
|
|
67
|
-
if (entries.length === 0) {
|
|
68
|
-
throw new Error(`No *.schema.json files found in ${dir}`);
|
|
69
|
-
}
|
|
70
|
-
return entries.map((file) => {
|
|
71
|
-
const abs = path.join(dir, file);
|
|
72
|
-
const raw = fs.readFileSync(abs, 'utf8');
|
|
73
|
-
let json;
|
|
74
|
-
try {
|
|
75
|
-
json = JSON.parse(raw);
|
|
76
|
-
} catch (err) {
|
|
77
|
-
throw new Error(`Failed to parse JSON schema ${file}: ${err.message}`);
|
|
78
|
-
}
|
|
79
|
-
const event = file.replace(/\.schema\.json$/, '');
|
|
80
|
-
const description =
|
|
81
|
-
typeof json.description === 'string' && json.description.trim().length > 0
|
|
82
|
-
? json.description.trim()
|
|
83
|
-
: '';
|
|
84
|
-
const required = Array.isArray(json.required) ? [...json.required] : [];
|
|
85
|
-
return { event, file, description, required };
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Escape pipe characters so they survive Markdown table cell parsing.
|
|
91
|
-
*
|
|
92
|
-
* @param {string} text
|
|
93
|
-
* @returns {string}
|
|
94
|
-
*/
|
|
95
|
-
function escapeCell(text) {
|
|
96
|
-
return text.replace(/\|/g, '\\|');
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Render the bounded-region body (the generated content between the two
|
|
101
|
-
* comment markers, exclusive of the markers themselves).
|
|
102
|
-
*
|
|
103
|
-
* @param {ReturnType<typeof readLifecycleSchemas>} rows
|
|
104
|
-
* @returns {string}
|
|
105
|
-
*/
|
|
106
|
-
function renderTable(rows) {
|
|
107
|
-
const header = '| Event | Schema | Description | Required fields |';
|
|
108
|
-
const sep = '| --- | --- | --- | --- |';
|
|
109
|
-
const body = rows.map((row) => {
|
|
110
|
-
const eventCell = `\`${row.event}\``;
|
|
111
|
-
const schemaLink = `[\`${row.file}\`](../.agents/schemas/lifecycle/${row.file})`;
|
|
112
|
-
const description = escapeCell(row.description || '—');
|
|
113
|
-
const requiredCell =
|
|
114
|
-
row.required.length === 0
|
|
115
|
-
? '—'
|
|
116
|
-
: row.required.map((field) => `\`${field}\``).join(', ');
|
|
117
|
-
return `| ${eventCell} | ${schemaLink} | ${description} | ${requiredCell} |`;
|
|
118
|
-
});
|
|
119
|
-
// Surround with blank lines so the markers + table read as a separate block.
|
|
120
|
-
return ['', header, sep, ...body, ''].join('\n');
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Substitute the bounded region inside `original`. Throws if either marker
|
|
125
|
-
* is missing, or if `BEGIN` appears after `END`. Idempotent — a re-run on
|
|
126
|
-
* the same input yields byte-identical output.
|
|
127
|
-
*
|
|
128
|
-
* @param {string} original
|
|
129
|
-
* @param {string} body Region body, already including leading/trailing blank
|
|
130
|
-
* lines (as produced by `renderTable`).
|
|
131
|
-
* @returns {string}
|
|
132
|
-
*/
|
|
133
|
-
function spliceRegion(original, body) {
|
|
134
|
-
const beginIdx = original.indexOf(REGION_BEGIN);
|
|
135
|
-
const endIdx = original.indexOf(REGION_END);
|
|
136
|
-
if (beginIdx === -1) {
|
|
137
|
-
throw new Error(
|
|
138
|
-
`Missing region marker "${REGION_BEGIN}" in ${DOC_PATH}. ` +
|
|
139
|
-
'Insert the marker pair before re-running the generator.',
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
if (endIdx === -1) {
|
|
143
|
-
throw new Error(
|
|
144
|
-
`Missing region marker "${REGION_END}" in ${DOC_PATH}. ` +
|
|
145
|
-
'Insert the marker pair before re-running the generator.',
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
if (endIdx < beginIdx) {
|
|
149
|
-
throw new Error(
|
|
150
|
-
`Region markers out of order in ${DOC_PATH}: END appears before BEGIN.`,
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
const before = original.slice(0, beginIdx + REGION_BEGIN.length);
|
|
154
|
-
const after = original.slice(endIdx);
|
|
155
|
-
return `${before}\n${body}\n${after}`;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Build the canonical post-generation file content for `docs/LIFECYCLE.md`.
|
|
160
|
-
*
|
|
161
|
-
* @param {string} schemaDir
|
|
162
|
-
* @param {string} docPath
|
|
163
|
-
* @returns {{ generated: string, original: string }}
|
|
164
|
-
*/
|
|
165
|
-
function buildExpected(schemaDir, docPath) {
|
|
166
|
-
if (!fs.existsSync(docPath)) {
|
|
167
|
-
throw new Error(`Target doc not found: ${docPath}`);
|
|
168
|
-
}
|
|
169
|
-
const original = fs.readFileSync(docPath, 'utf8');
|
|
170
|
-
const rows = readLifecycleSchemas(schemaDir);
|
|
171
|
-
const body = renderTable(rows);
|
|
172
|
-
const generated = spliceRegion(original, body);
|
|
173
|
-
return { generated, original };
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* @param {string[]} argv
|
|
178
|
-
*/
|
|
179
|
-
async function main(argv = process.argv.slice(2)) {
|
|
180
|
-
const { values } = parseArgs({
|
|
181
|
-
args: argv,
|
|
182
|
-
options: {
|
|
183
|
-
check: { type: 'boolean', default: false },
|
|
184
|
-
},
|
|
185
|
-
allowPositionals: false,
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
const { generated, original } = buildExpected(SCHEMA_DIR, DOC_PATH);
|
|
189
|
-
|
|
190
|
-
if (values.check) {
|
|
191
|
-
if (generated === original) {
|
|
192
|
-
Logger.info(
|
|
193
|
-
`generate-lifecycle-docs: ${path.relative(PROJECT_ROOT, DOC_PATH)} is up to date.`,
|
|
194
|
-
);
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
const hint =
|
|
198
|
-
`${path.relative(PROJECT_ROOT, DOC_PATH)} is out of date. ` +
|
|
199
|
-
'Run `node .agents/scripts/generate-lifecycle-docs.js` to regenerate the bounded region.';
|
|
200
|
-
throw new Error(hint);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (generated === original) {
|
|
204
|
-
Logger.info(
|
|
205
|
-
`generate-lifecycle-docs: ${path.relative(PROJECT_ROOT, DOC_PATH)} already current — no write.`,
|
|
206
|
-
);
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
fs.writeFileSync(DOC_PATH, generated, 'utf8');
|
|
210
|
-
Logger.info(
|
|
211
|
-
`generate-lifecycle-docs: wrote bounded region into ${path.relative(PROJECT_ROOT, DOC_PATH)}.`,
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
export {
|
|
216
|
-
buildExpected,
|
|
217
|
-
REGION_BEGIN,
|
|
218
|
-
REGION_END,
|
|
219
|
-
readLifecycleSchemas,
|
|
220
|
-
renderTable,
|
|
221
|
-
spliceRegion,
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
runAsCli(import.meta.url, main, {
|
|
225
|
-
source: 'generate-lifecycle-docs',
|
|
226
|
-
usage: {
|
|
227
|
-
invocation: 'node .agents/scripts/generate-lifecycle-docs.js [--check]',
|
|
228
|
-
summary:
|
|
229
|
-
'Regenerate the lifecycle-event table in docs/LIFECYCLE.md from the event schemas. Writes only when the generated content differs.',
|
|
230
|
-
flags: [
|
|
231
|
-
[
|
|
232
|
-
'--check',
|
|
233
|
-
'Verify the doc is current and fail if stale; write nothing.',
|
|
234
|
-
],
|
|
235
|
-
],
|
|
236
|
-
},
|
|
237
|
-
});
|