@kungfu-tech/buildchain 3.0.3 → 3.0.4-alpha.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 +13 -0
- package/dist/site/buildchain-contract.json +5 -5
- package/dist/site/buildchain-site.json +7 -7
- package/dist/site/capability-registry.json +1 -1
- package/dist/site/cli-registry.json +18 -0
- package/dist/site/kfd-claims.json +19 -7
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +1 -1
- package/dist/site/node-api-registry.json +2 -2
- package/dist/site/page-registry.json +2 -2
- package/dist/site/public-surface-audit.json +14 -4
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +5 -5
- package/docs/cli.md +22 -7
- package/package.json +1 -1
- package/packages/core/paper-agent-entry.js +361 -0
- package/packages/core/paper-repository.js +2 -0
- package/packages/core/paper-scaffold-content.js +163 -0
- package/packages/core/paper-work.js +35 -0
- package/packages/core/paper.js +139 -170
- package/scripts/buildchain-cli-help.mjs +1 -0
- package/scripts/paper-agent-cli.mjs +35 -0
- package/scripts/paper-work-fleet-cli.mjs +14 -4
- package/scripts/paper.mjs +15 -2
- package/scripts/site-capability-metadata.mjs +1 -1
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
PAPER_PATHS,
|
|
6
|
+
PAPER_WORK_BRANCH_PATTERN,
|
|
7
|
+
gitResult,
|
|
8
|
+
gitValue,
|
|
9
|
+
paperDevelopmentRef,
|
|
10
|
+
readJson,
|
|
11
|
+
sha256Text,
|
|
12
|
+
stableJson,
|
|
13
|
+
workCheck,
|
|
14
|
+
} from "./paper-repository.js";
|
|
15
|
+
|
|
16
|
+
export const PAPER_AGENT_ENTRY_CONTRACT = "kungfu-buildchain-paper-agent-entry";
|
|
17
|
+
export const PAPER_AGENT_ENTRY_SCHEMA_VERSION = 1;
|
|
18
|
+
export const PAPER_AGENT_ENTRY_SECTION_START =
|
|
19
|
+
"<!-- buildchain:paper-agent-entry:v1:start -->";
|
|
20
|
+
export const PAPER_AGENT_ENTRY_SECTION_END =
|
|
21
|
+
"<!-- buildchain:paper-agent-entry:v1:end -->";
|
|
22
|
+
|
|
23
|
+
const EXACT_PAPER_SCRIPTS = Object.freeze({
|
|
24
|
+
"paper:agent:verify": "buildchain paper agent verify --json",
|
|
25
|
+
"paper:work:start": "buildchain paper work start",
|
|
26
|
+
"paper:work:submit": "buildchain paper work submit",
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const GIT_SHA_PATTERN = /^[0-9a-f]{40}$/i;
|
|
30
|
+
const SHA256_PATTERN = /^sha256:[0-9a-f]{64}$/i;
|
|
31
|
+
|
|
32
|
+
function jsonText(value) {
|
|
33
|
+
return `${JSON.stringify(value, null, 2)}\n`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function paperAgentEntryInstructions({ developmentRef }) {
|
|
37
|
+
return `${PAPER_AGENT_ENTRY_SECTION_START}
|
|
38
|
+
## Paper work entry (required)
|
|
39
|
+
|
|
40
|
+
This repository uses the Buildchain-managed Paper path. Before changing any
|
|
41
|
+
tracked file, inspect the current branch and follow the matching rule:
|
|
42
|
+
|
|
43
|
+
- On \`main\`, \`${developmentRef}\`, or another protected channel, do not edit.
|
|
44
|
+
Switch to the exact current \`${developmentRef}\`, install the pinned pnpm
|
|
45
|
+
dependencies when needed, then run
|
|
46
|
+
\`pnpm paper:work:start -- <topic> --execute --json\`.
|
|
47
|
+
- On an existing \`feature/*\`, \`fix/*\`, \`docs/*\`, \`chore/*\`, \`ci/*\`, or
|
|
48
|
+
\`refactor/*\` branch, run \`pnpm paper:agent:verify\` before continuing.
|
|
49
|
+
- Before opening or updating a pull request, commit the intended bytes and run
|
|
50
|
+
\`pnpm paper:work:submit -- --execute --json\`. The target must be
|
|
51
|
+
\`${developmentRef}\`; direct protected-branch pushes and force pushes are
|
|
52
|
+
forbidden.
|
|
53
|
+
|
|
54
|
+
The required remote check independently verifies this contract, the exact
|
|
55
|
+
Buildchain package and runtime authority, and feature-to-development lineage.
|
|
56
|
+
Passing local commands alone is not acceptance, and manually bypassing them
|
|
57
|
+
does not bypass remote policy.
|
|
58
|
+
${PAPER_AGENT_ENTRY_SECTION_END}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function mergePaperAgentEntryInstructions(
|
|
62
|
+
current = "",
|
|
63
|
+
{ developmentRef },
|
|
64
|
+
) {
|
|
65
|
+
const source = String(current || "");
|
|
66
|
+
const section = paperAgentEntryInstructions({ developmentRef });
|
|
67
|
+
const start = source.indexOf(PAPER_AGENT_ENTRY_SECTION_START);
|
|
68
|
+
const end = source.indexOf(PAPER_AGENT_ENTRY_SECTION_END);
|
|
69
|
+
if ((start === -1) !== (end === -1)) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
"AGENTS.md has an incomplete Buildchain Paper agent-entry managed section",
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
if (start === -1) {
|
|
75
|
+
return source.trim()
|
|
76
|
+
? `${source.trimEnd()}\n\n${section}\n`
|
|
77
|
+
: `# AGENTS.md\n\n${section}\n`;
|
|
78
|
+
}
|
|
79
|
+
if (
|
|
80
|
+
source.indexOf(PAPER_AGENT_ENTRY_SECTION_START, start + 1) !== -1 ||
|
|
81
|
+
source.indexOf(PAPER_AGENT_ENTRY_SECTION_END, end + 1) !== -1 ||
|
|
82
|
+
end < start
|
|
83
|
+
) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
"AGENTS.md has ambiguous Buildchain Paper agent-entry managed sections",
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
return `${source.slice(0, start)}${section}${source.slice(
|
|
89
|
+
end + PAPER_AGENT_ENTRY_SECTION_END.length,
|
|
90
|
+
)}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function createPaperAgentEntry({
|
|
94
|
+
buildchainVersion,
|
|
95
|
+
buildchainSha,
|
|
96
|
+
developmentRef,
|
|
97
|
+
}) {
|
|
98
|
+
const version = String(buildchainVersion || "").trim();
|
|
99
|
+
const sourceSha = String(buildchainSha || "").trim();
|
|
100
|
+
if (!/^3\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version)) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
"paper agent entry requires an exact Buildchain v3 version",
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
if (!GIT_SHA_PATTERN.test(sourceSha)) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
"paper agent entry requires an exact Buildchain source SHA",
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
if (!/^dev\/v\d+\/v\d+\.\d+$/.test(developmentRef)) {
|
|
111
|
+
throw new Error("paper agent entry requires an exact development ref");
|
|
112
|
+
}
|
|
113
|
+
const payload = {
|
|
114
|
+
schemaVersion: PAPER_AGENT_ENTRY_SCHEMA_VERSION,
|
|
115
|
+
contract: PAPER_AGENT_ENTRY_CONTRACT,
|
|
116
|
+
runtime: {
|
|
117
|
+
package: "@kungfu-tech/buildchain",
|
|
118
|
+
version,
|
|
119
|
+
sourceSha,
|
|
120
|
+
},
|
|
121
|
+
repository: {
|
|
122
|
+
developmentRef,
|
|
123
|
+
pullRequestTarget: developmentRef,
|
|
124
|
+
},
|
|
125
|
+
commands: {
|
|
126
|
+
verify: "pnpm paper:agent:verify",
|
|
127
|
+
start: "pnpm paper:work:start -- <topic> --execute --json",
|
|
128
|
+
submit: "pnpm paper:work:submit -- --execute --json",
|
|
129
|
+
},
|
|
130
|
+
policy: {
|
|
131
|
+
localMutation: "dry-run-first",
|
|
132
|
+
protectedBranchPatterns: [
|
|
133
|
+
"main",
|
|
134
|
+
"dev/**",
|
|
135
|
+
"alpha/**",
|
|
136
|
+
"release/**",
|
|
137
|
+
"publish-gate/**",
|
|
138
|
+
],
|
|
139
|
+
workBranchPattern: PAPER_WORK_BRANCH_PATTERN.source,
|
|
140
|
+
forcePush: false,
|
|
141
|
+
remoteAcceptance: "required-buildchain-check",
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
return {
|
|
145
|
+
...payload,
|
|
146
|
+
entryDigest: sha256Text(stableJson(payload)),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function paperAgentEntryFiles({
|
|
151
|
+
cwd,
|
|
152
|
+
buildchainVersion,
|
|
153
|
+
buildchainSha,
|
|
154
|
+
developmentRef = "",
|
|
155
|
+
}) {
|
|
156
|
+
const resolvedDevelopmentRef = developmentRef || paperDevelopmentRef(cwd);
|
|
157
|
+
const agentsPath = path.resolve(cwd, PAPER_PATHS.agentInstructions);
|
|
158
|
+
const currentAgents = fs.existsSync(agentsPath)
|
|
159
|
+
? fs.readFileSync(agentsPath, "utf8")
|
|
160
|
+
: "";
|
|
161
|
+
const entry = createPaperAgentEntry({
|
|
162
|
+
buildchainVersion,
|
|
163
|
+
buildchainSha,
|
|
164
|
+
developmentRef: resolvedDevelopmentRef,
|
|
165
|
+
});
|
|
166
|
+
return new Map([
|
|
167
|
+
[PAPER_PATHS.agentEntry, jsonText(entry)],
|
|
168
|
+
[
|
|
169
|
+
PAPER_PATHS.agentInstructions,
|
|
170
|
+
mergePaperAgentEntryInstructions(currentAgents, {
|
|
171
|
+
developmentRef: resolvedDevelopmentRef,
|
|
172
|
+
}),
|
|
173
|
+
],
|
|
174
|
+
]);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function resolvePaperBuildchainSha(buildchainRoot, buildchainSha = "") {
|
|
178
|
+
if (buildchainSha) return buildchainSha;
|
|
179
|
+
if (!fs.existsSync(path.resolve(buildchainRoot, ".git"))) return "";
|
|
180
|
+
const observed = gitValue(buildchainRoot, ["rev-parse", "HEAD"]);
|
|
181
|
+
return GIT_SHA_PATTERN.test(observed) ? observed : "";
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function expectedCiBranches(developmentRef) {
|
|
185
|
+
return [
|
|
186
|
+
developmentRef,
|
|
187
|
+
developmentRef.replace(/^dev\//, "alpha/"),
|
|
188
|
+
developmentRef.replace(/^dev\//, "release/"),
|
|
189
|
+
];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function ciContext({ env, developmentRef }) {
|
|
193
|
+
const event = String(env.GITHUB_EVENT_NAME || "");
|
|
194
|
+
const sourceBranch = String(
|
|
195
|
+
env.GITHUB_HEAD_REF || env.BUILDCHAIN_PAPER_SOURCE_BRANCH || "",
|
|
196
|
+
);
|
|
197
|
+
const targetBranch = String(
|
|
198
|
+
env.GITHUB_BASE_REF || env.BUILDCHAIN_PAPER_TARGET_BRANCH || "",
|
|
199
|
+
);
|
|
200
|
+
const refName = String(
|
|
201
|
+
env.GITHUB_REF_NAME || env.BUILDCHAIN_PAPER_REF_NAME || "",
|
|
202
|
+
);
|
|
203
|
+
const pullRequest = ["pull_request", "pull_request_target"].includes(event);
|
|
204
|
+
const branchOk = pullRequest
|
|
205
|
+
? PAPER_WORK_BRANCH_PATTERN.test(sourceBranch) &&
|
|
206
|
+
targetBranch === developmentRef
|
|
207
|
+
: expectedCiBranches(developmentRef).includes(refName);
|
|
208
|
+
return {
|
|
209
|
+
mode: "ci",
|
|
210
|
+
event,
|
|
211
|
+
sourceBranch,
|
|
212
|
+
targetBranch,
|
|
213
|
+
refName,
|
|
214
|
+
pullRequest,
|
|
215
|
+
ok: branchOk,
|
|
216
|
+
message: pullRequest
|
|
217
|
+
? `Pull requests must use an allowed work branch and target ${developmentRef}.`
|
|
218
|
+
: `Channel checks must run on ${expectedCiBranches(developmentRef).join(", ")}.`,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function localContext({ cwd, developmentRef }) {
|
|
223
|
+
const branch = gitValue(cwd, ["branch", "--show-current"]);
|
|
224
|
+
const developmentSha = gitValue(cwd, [
|
|
225
|
+
"rev-parse",
|
|
226
|
+
"--verify",
|
|
227
|
+
`refs/remotes/origin/${developmentRef}`,
|
|
228
|
+
]);
|
|
229
|
+
const containsDevelopment =
|
|
230
|
+
PAPER_WORK_BRANCH_PATTERN.test(branch) &&
|
|
231
|
+
GIT_SHA_PATTERN.test(developmentSha) &&
|
|
232
|
+
gitResult(cwd, ["merge-base", "--is-ancestor", developmentSha, "HEAD"]).ok;
|
|
233
|
+
return {
|
|
234
|
+
mode: "local",
|
|
235
|
+
branch,
|
|
236
|
+
developmentRef,
|
|
237
|
+
developmentSha,
|
|
238
|
+
ok: containsDevelopment,
|
|
239
|
+
message: PAPER_WORK_BRANCH_PATTERN.test(branch)
|
|
240
|
+
? `The work branch must contain origin/${developmentRef}.`
|
|
241
|
+
: `Run paper work start from the exact ${developmentRef} channel before editing.`,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function collectPaperAgentEntry({
|
|
246
|
+
cwd = process.cwd(),
|
|
247
|
+
buildchainSha = "",
|
|
248
|
+
mode = "contract",
|
|
249
|
+
env = process.env,
|
|
250
|
+
} = {}) {
|
|
251
|
+
const resolvedCwd = path.resolve(cwd);
|
|
252
|
+
const developmentRef = paperDevelopmentRef(resolvedCwd);
|
|
253
|
+
const source = readJson(path.resolve(resolvedCwd, PAPER_PATHS.agentEntry));
|
|
254
|
+
const entry = source.value || {};
|
|
255
|
+
const effectiveBuildchainSha =
|
|
256
|
+
buildchainSha || String(entry.runtime?.sourceSha || "");
|
|
257
|
+
const packageJson =
|
|
258
|
+
readJson(path.resolve(resolvedCwd, "package.json")).value || {};
|
|
259
|
+
const pinPath = path.resolve(resolvedCwd, PAPER_PATHS.versionPin);
|
|
260
|
+
const versionPin = fs.existsSync(pinPath)
|
|
261
|
+
? fs.readFileSync(pinPath, "utf8").trim()
|
|
262
|
+
: "";
|
|
263
|
+
const agentsPath = path.resolve(resolvedCwd, PAPER_PATHS.agentInstructions);
|
|
264
|
+
const agents = fs.existsSync(agentsPath)
|
|
265
|
+
? fs.readFileSync(agentsPath, "utf8")
|
|
266
|
+
: "";
|
|
267
|
+
const expectedInstructions = paperAgentEntryInstructions({ developmentRef });
|
|
268
|
+
const { entryDigest: observedDigest, ...entryPayload } = entry;
|
|
269
|
+
const checks = [
|
|
270
|
+
workCheck(
|
|
271
|
+
"agent-entry.contract",
|
|
272
|
+
source.exists &&
|
|
273
|
+
!source.error &&
|
|
274
|
+
entry.contract === PAPER_AGENT_ENTRY_CONTRACT &&
|
|
275
|
+
entry.schemaVersion === PAPER_AGENT_ENTRY_SCHEMA_VERSION,
|
|
276
|
+
"The versioned Paper agent-entry contract exists and parses.",
|
|
277
|
+
"buildchain paper migrate --write --json",
|
|
278
|
+
),
|
|
279
|
+
workCheck(
|
|
280
|
+
"agent-entry.digest",
|
|
281
|
+
SHA256_PATTERN.test(String(observedDigest || "")) &&
|
|
282
|
+
observedDigest === sha256Text(stableJson(entryPayload)),
|
|
283
|
+
"The Paper agent-entry contract digest matches its exact payload.",
|
|
284
|
+
"buildchain paper migrate --write --json",
|
|
285
|
+
),
|
|
286
|
+
workCheck(
|
|
287
|
+
"agent-entry.instructions",
|
|
288
|
+
agents.includes(expectedInstructions) &&
|
|
289
|
+
agents.split(PAPER_AGENT_ENTRY_SECTION_START).length === 2 &&
|
|
290
|
+
agents.split(PAPER_AGENT_ENTRY_SECTION_END).length === 2,
|
|
291
|
+
"AGENTS.md contains exactly one current Buildchain-managed Paper entry section.",
|
|
292
|
+
"buildchain paper migrate --write --json",
|
|
293
|
+
),
|
|
294
|
+
workCheck(
|
|
295
|
+
"agent-entry.package-version",
|
|
296
|
+
Boolean(versionPin) &&
|
|
297
|
+
entry.runtime?.version === versionPin &&
|
|
298
|
+
packageJson.devDependencies?.["@kungfu-tech/buildchain"] === versionPin,
|
|
299
|
+
"The agent-entry contract, version pin, and exact Buildchain dependency agree.",
|
|
300
|
+
"buildchain paper migrate --write --json && pnpm install --lockfile-only",
|
|
301
|
+
),
|
|
302
|
+
workCheck(
|
|
303
|
+
"agent-entry.runtime-source",
|
|
304
|
+
GIT_SHA_PATTERN.test(effectiveBuildchainSha) &&
|
|
305
|
+
entry.runtime?.sourceSha === effectiveBuildchainSha,
|
|
306
|
+
"The agent-entry contract is bound to the exact executing Buildchain source SHA.",
|
|
307
|
+
"buildchain paper migrate --write --json",
|
|
308
|
+
),
|
|
309
|
+
workCheck(
|
|
310
|
+
"agent-entry.development-ref",
|
|
311
|
+
entry.repository?.developmentRef === developmentRef &&
|
|
312
|
+
entry.repository?.pullRequestTarget === developmentRef,
|
|
313
|
+
`The agent-entry contract targets ${developmentRef}.`,
|
|
314
|
+
"buildchain paper migrate --write --json",
|
|
315
|
+
),
|
|
316
|
+
...Object.entries(EXACT_PAPER_SCRIPTS).map(([name, command]) =>
|
|
317
|
+
workCheck(
|
|
318
|
+
`agent-entry.script.${name}`,
|
|
319
|
+
packageJson.scripts?.[name] === command,
|
|
320
|
+
`package.json exposes the exact ${name} command.`,
|
|
321
|
+
"buildchain paper migrate --write --json",
|
|
322
|
+
),
|
|
323
|
+
),
|
|
324
|
+
];
|
|
325
|
+
const context =
|
|
326
|
+
mode === "ci"
|
|
327
|
+
? ciContext({ env, developmentRef })
|
|
328
|
+
: mode === "local"
|
|
329
|
+
? localContext({ cwd: resolvedCwd, developmentRef })
|
|
330
|
+
: { mode: "contract", ok: true };
|
|
331
|
+
if (mode !== "contract") {
|
|
332
|
+
checks.push(
|
|
333
|
+
workCheck(
|
|
334
|
+
"agent-entry.work-context",
|
|
335
|
+
context.ok,
|
|
336
|
+
context.message,
|
|
337
|
+
mode === "ci"
|
|
338
|
+
? "retarget the pull request to the configured development channel"
|
|
339
|
+
: `pnpm paper:work:start -- <topic> --execute --json`,
|
|
340
|
+
),
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
const ok = checks.every((entryCheck) => entryCheck.status === "pass");
|
|
344
|
+
return {
|
|
345
|
+
schemaVersion: 1,
|
|
346
|
+
contract: PAPER_AGENT_ENTRY_CONTRACT,
|
|
347
|
+
ok,
|
|
348
|
+
cwd: resolvedCwd,
|
|
349
|
+
mode,
|
|
350
|
+
entry,
|
|
351
|
+
context,
|
|
352
|
+
checks,
|
|
353
|
+
nextActions: checks
|
|
354
|
+
.filter((entryCheck) => entryCheck.status === "fail")
|
|
355
|
+
.map((entryCheck) => ({
|
|
356
|
+
id: `repair-${entryCheck.id}`,
|
|
357
|
+
command: entryCheck.correctiveCommand,
|
|
358
|
+
description: entryCheck.message,
|
|
359
|
+
})),
|
|
360
|
+
};
|
|
361
|
+
}
|
|
@@ -6,6 +6,8 @@ import { loadBuildchainConfig } from "./buildchain-config.js";
|
|
|
6
6
|
|
|
7
7
|
export const PAPER_PATHS = Object.freeze({
|
|
8
8
|
config: ".buildchain/buildchain.toml",
|
|
9
|
+
agentEntry: ".buildchain/paper/agent-entry.json",
|
|
10
|
+
agentInstructions: "AGENTS.md",
|
|
9
11
|
versionPin: ".buildchain-version",
|
|
10
12
|
contractLock: ".buildchain/contract-lock.json",
|
|
11
13
|
buildWorkflow: ".github/workflows/build.yml",
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
function jsonText(value) {
|
|
2
|
+
return `${JSON.stringify(value, null, 2)}\n`;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function texEscape(value) {
|
|
6
|
+
return String(value || "")
|
|
7
|
+
.replaceAll("\\", "\\textbackslash{}")
|
|
8
|
+
.replaceAll("&", "\\&")
|
|
9
|
+
.replaceAll("%", "\\%")
|
|
10
|
+
.replaceAll("$", "\\$")
|
|
11
|
+
.replaceAll("#", "\\#")
|
|
12
|
+
.replaceAll("_", "\\_")
|
|
13
|
+
.replaceAll("{", "\\{")
|
|
14
|
+
.replaceAll("}", "\\}");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function scaffoldMakefile({ image, digest, command }) {
|
|
18
|
+
return `.PHONY: check pdf clean
|
|
19
|
+
|
|
20
|
+
BUILDER_IMAGE := ${image}@${digest}
|
|
21
|
+
SOURCE_DATE_EPOCH ?= 0
|
|
22
|
+
|
|
23
|
+
check:
|
|
24
|
+
\t@test -f paper/main.tex
|
|
25
|
+
\t@test -f paper/references.bib
|
|
26
|
+
\t@git diff --check
|
|
27
|
+
|
|
28
|
+
pdf:
|
|
29
|
+
\t@mkdir -p _build
|
|
30
|
+
\tdocker run --rm --network=none \\
|
|
31
|
+
\t\t-e SOURCE_DATE_EPOCH="$(SOURCE_DATE_EPOCH)" \\
|
|
32
|
+
\t\t-e TZ=UTC -e LANG=C.UTF-8 -e LC_ALL=C.UTF-8 -e HOME=/tmp \\
|
|
33
|
+
\t\t-v "$(CURDIR):/workspace" -w /workspace \\
|
|
34
|
+
\t\t"$(BUILDER_IMAGE)" bash -lc '${command}'
|
|
35
|
+
|
|
36
|
+
clean:
|
|
37
|
+
\trm -rf _build
|
|
38
|
+
`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function paperPackageScripts(current = {}) {
|
|
42
|
+
return {
|
|
43
|
+
...current,
|
|
44
|
+
"buildchain:paper": "buildchain paper",
|
|
45
|
+
"paper:preflight": "buildchain paper preflight --json",
|
|
46
|
+
"paper:agent:verify": "buildchain paper agent verify --json",
|
|
47
|
+
"paper:work:start": "buildchain paper work start",
|
|
48
|
+
"paper:work:submit": "buildchain paper work submit",
|
|
49
|
+
"paper:status": "buildchain paper status --json",
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function managedPaperPackageJson(current, buildchainVersion) {
|
|
54
|
+
if (!/^3\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(buildchainVersion)) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
"paper repositories require an exact Buildchain v3 version",
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
const packageManager = current.packageManager || "pnpm@11.7.0";
|
|
60
|
+
if (!packageManager.startsWith("pnpm@")) {
|
|
61
|
+
throw new Error("paper repositories require a pnpm packageManager");
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
...current,
|
|
65
|
+
private: true,
|
|
66
|
+
scripts: paperPackageScripts(current.scripts),
|
|
67
|
+
devDependencies: {
|
|
68
|
+
...(current.devDependencies || {}),
|
|
69
|
+
"@kungfu-tech/buildchain": buildchainVersion,
|
|
70
|
+
},
|
|
71
|
+
packageManager,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function scaffoldPackageJson({
|
|
76
|
+
name,
|
|
77
|
+
packageName,
|
|
78
|
+
repository,
|
|
79
|
+
buildchainVersion,
|
|
80
|
+
}) {
|
|
81
|
+
return jsonText({
|
|
82
|
+
...managedPaperPackageJson(
|
|
83
|
+
{
|
|
84
|
+
name: packageName,
|
|
85
|
+
description: `${name} publication source repository.`,
|
|
86
|
+
repository: {
|
|
87
|
+
type: "git",
|
|
88
|
+
url: `git+https://github.com/${repository}.git`,
|
|
89
|
+
},
|
|
90
|
+
license: "Apache-2.0",
|
|
91
|
+
},
|
|
92
|
+
buildchainVersion,
|
|
93
|
+
),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function scaffoldReadme({ title, packageName }) {
|
|
98
|
+
return `# ${title}
|
|
99
|
+
|
|
100
|
+
This repository is a Buildchain-governed publication artifact source.
|
|
101
|
+
|
|
102
|
+
## Local workflow
|
|
103
|
+
|
|
104
|
+
\`\`\`sh
|
|
105
|
+
pnpm paper:preflight
|
|
106
|
+
pnpm paper:agent:verify
|
|
107
|
+
pnpm paper:work:start -- <topic>
|
|
108
|
+
pnpm paper:work:submit
|
|
109
|
+
make pdf
|
|
110
|
+
\`\`\`
|
|
111
|
+
|
|
112
|
+
The public package identity is \`${packageName}\`. Buildchain owns reproducible
|
|
113
|
+
artifact generation, sealed publication, npm Trusted Publishing, and recovery;
|
|
114
|
+
this repository owns the paper source and review history.
|
|
115
|
+
|
|
116
|
+
See [docs/MAP.md](docs/MAP.md) for the repository map.
|
|
117
|
+
`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function scaffoldMap() {
|
|
121
|
+
return `# Repository Map
|
|
122
|
+
|
|
123
|
+
- \`paper/main.tex\`: paper source entrypoint.
|
|
124
|
+
- \`paper/references.bib\`: bibliography source.
|
|
125
|
+
- \`AGENTS.md\`: mandatory managed entry instructions for people and coding agents.
|
|
126
|
+
- \`.buildchain/paper/agent-entry.json\`: versioned, digest-bound local and CI entry policy.
|
|
127
|
+
- \`.buildchain/buildchain.toml\`: publication identity, toolchain, package, and lifecycle contract.
|
|
128
|
+
- \`.buildchain/contract-lock.json\`: accepted Buildchain runtime contract.
|
|
129
|
+
- \`.github/workflows/build.yml\`: thin read-only build and reproducibility caller.
|
|
130
|
+
- \`.github/workflows/verify.yml\`: thin required check that enforces the Paper entry and acceptance policy.
|
|
131
|
+
- \`.github/workflows/paper-release.yml\`: thin protected sealed-release caller.
|
|
132
|
+
|
|
133
|
+
The repository does not own npm transaction logic, publication authority,
|
|
134
|
+
release-state recovery, or site deployment mechanics. Those remain Buildchain,
|
|
135
|
+
npm/GitHub, and downstream site responsibilities respectively.
|
|
136
|
+
`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function scaffoldMainTex(title) {
|
|
140
|
+
return `\\documentclass[11pt]{article}
|
|
141
|
+
\\usepackage[T1]{fontenc}
|
|
142
|
+
\\usepackage{lmodern}
|
|
143
|
+
\\usepackage{hyperref}
|
|
144
|
+
|
|
145
|
+
\\title{${texEscape(title)}}
|
|
146
|
+
\\author{}
|
|
147
|
+
\\date{}
|
|
148
|
+
|
|
149
|
+
\\begin{document}
|
|
150
|
+
\\maketitle
|
|
151
|
+
|
|
152
|
+
\\begin{abstract}
|
|
153
|
+
Replace this paragraph with the paper abstract.
|
|
154
|
+
\\end{abstract}
|
|
155
|
+
|
|
156
|
+
\\section{Introduction}
|
|
157
|
+
Replace this section with the reviewed paper content.
|
|
158
|
+
|
|
159
|
+
\\bibliographystyle{plain}
|
|
160
|
+
\\bibliography{paper/references}
|
|
161
|
+
\\end{document}
|
|
162
|
+
`;
|
|
163
|
+
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { collectPaperAgentEntry } from "./paper-agent-entry.js";
|
|
2
3
|
import {
|
|
4
|
+
PAPER_PATHS,
|
|
3
5
|
PAPER_WORK_BRANCH_PATTERN,
|
|
4
6
|
gitResult,
|
|
5
7
|
gitValue,
|
|
6
8
|
normalizedWorkBranch,
|
|
7
9
|
paperDevelopmentRef,
|
|
8
10
|
paperWorkSource,
|
|
11
|
+
readJson,
|
|
9
12
|
remoteBranchObservation,
|
|
10
13
|
rootedPlan,
|
|
11
14
|
workCheck,
|
|
@@ -26,10 +29,20 @@ function failedActions(checks) {
|
|
|
26
29
|
}));
|
|
27
30
|
}
|
|
28
31
|
|
|
32
|
+
function agentEntryForWork(cwd, buildchainSha) {
|
|
33
|
+
const policy = readJson(path.resolve(cwd, PAPER_PATHS.agentEntry)).value;
|
|
34
|
+
return collectPaperAgentEntry({
|
|
35
|
+
cwd,
|
|
36
|
+
buildchainSha: buildchainSha || policy?.runtime?.sourceSha || "",
|
|
37
|
+
mode: "contract",
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
29
41
|
export function createPaperWorkStartPlan({
|
|
30
42
|
cwd = process.cwd(),
|
|
31
43
|
topic = "",
|
|
32
44
|
branch = "",
|
|
45
|
+
buildchainSha = "",
|
|
33
46
|
} = {}) {
|
|
34
47
|
const resolvedCwd = path.resolve(cwd);
|
|
35
48
|
const source = paperWorkSource(resolvedCwd);
|
|
@@ -56,7 +69,14 @@ export function createPaperWorkStartPlan({
|
|
|
56
69
|
`${remoteDevelopment.sha}^{commit}`,
|
|
57
70
|
]).ok
|
|
58
71
|
: false;
|
|
72
|
+
const agentEntry = agentEntryForWork(resolvedCwd, buildchainSha);
|
|
59
73
|
const checks = [
|
|
74
|
+
workCheck(
|
|
75
|
+
"agent-entry.current",
|
|
76
|
+
agentEntry.ok,
|
|
77
|
+
"The mandatory Buildchain Paper agent-entry contract is current.",
|
|
78
|
+
"buildchain paper migrate --write --json",
|
|
79
|
+
),
|
|
60
80
|
workCheck(
|
|
61
81
|
"repository.canonical-origin",
|
|
62
82
|
source.canonical,
|
|
@@ -121,6 +141,9 @@ export function createPaperWorkStartPlan({
|
|
|
121
141
|
developmentRef,
|
|
122
142
|
remoteDevelopmentSha: remoteDevelopment.sha,
|
|
123
143
|
},
|
|
144
|
+
runtime: {
|
|
145
|
+
sourceSha: buildchainSha || agentEntry.entry?.runtime?.sourceSha || "",
|
|
146
|
+
},
|
|
124
147
|
target: { branch: targetBranch, startSha: remoteDevelopment.sha },
|
|
125
148
|
checks,
|
|
126
149
|
mutation: {
|
|
@@ -155,6 +178,7 @@ export function executePaperWorkStart(plan) {
|
|
|
155
178
|
const fresh = createPaperWorkStartPlan({
|
|
156
179
|
cwd: plan.cwd,
|
|
157
180
|
branch: plan.target.branch,
|
|
181
|
+
buildchainSha: plan.runtime?.sourceSha || "",
|
|
158
182
|
});
|
|
159
183
|
if (!fresh.ok || fresh.planRoot !== plan.planRoot) {
|
|
160
184
|
return {
|
|
@@ -184,6 +208,7 @@ export function createPaperWorkSubmitPlan({
|
|
|
184
208
|
cwd = process.cwd(),
|
|
185
209
|
pullRequests = [],
|
|
186
210
|
pullRequestObservation = { ok: true },
|
|
211
|
+
buildchainSha = "",
|
|
187
212
|
} = {}) {
|
|
188
213
|
const resolvedCwd = path.resolve(cwd);
|
|
189
214
|
const source = paperWorkSource(resolvedCwd);
|
|
@@ -222,7 +247,14 @@ export function createPaperWorkSubmitPlan({
|
|
|
222
247
|
entry.headRefName === source.branch &&
|
|
223
248
|
entry.baseRefName === developmentRef,
|
|
224
249
|
);
|
|
250
|
+
const agentEntry = agentEntryForWork(resolvedCwd, buildchainSha);
|
|
225
251
|
const checks = [
|
|
252
|
+
workCheck(
|
|
253
|
+
"agent-entry.current",
|
|
254
|
+
agentEntry.ok,
|
|
255
|
+
"The mandatory Buildchain Paper agent-entry contract is current.",
|
|
256
|
+
"buildchain paper migrate --write --json",
|
|
257
|
+
),
|
|
226
258
|
workCheck(
|
|
227
259
|
"repository.canonical-origin",
|
|
228
260
|
source.canonical,
|
|
@@ -292,6 +324,9 @@ export function createPaperWorkSubmitPlan({
|
|
|
292
324
|
remoteSha: remoteWork.sha,
|
|
293
325
|
},
|
|
294
326
|
target: { branch: developmentRef, sha: remoteDevelopment.sha },
|
|
327
|
+
runtime: {
|
|
328
|
+
sourceSha: buildchainSha || agentEntry.entry?.runtime?.sourceSha || "",
|
|
329
|
+
},
|
|
295
330
|
pullRequest: matchingPullRequest || null,
|
|
296
331
|
checks,
|
|
297
332
|
mutation: {
|