@clossys/launcher 0.3.1 → 0.5.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/README.md +1373 -60
- package/contracts/conversation-contract.md +2 -2
- package/contracts/product-ci-workflow.yml +74 -0
- package/contracts/repository-inventory.json +53 -0
- package/dist/admission-fixture.d.ts +168 -0
- package/dist/admission-fixture.d.ts.map +1 -0
- package/dist/admission-fixture.js +467 -0
- package/dist/admission-fixture.js.map +1 -0
- package/dist/admission.d.ts +124 -0
- package/dist/admission.d.ts.map +1 -0
- package/dist/admission.js +804 -0
- package/dist/admission.js.map +1 -0
- package/dist/agents-guide.d.ts +9 -0
- package/dist/agents-guide.d.ts.map +1 -0
- package/dist/agents-guide.js +26 -0
- package/dist/agents-guide.js.map +1 -0
- package/dist/apply-command-options.check.d.ts +12 -0
- package/dist/apply-command-options.check.d.ts.map +1 -0
- package/dist/apply-command-options.check.js +20 -0
- package/dist/apply-command-options.check.js.map +1 -0
- package/dist/apply-plan-cli.d.ts +39 -1
- package/dist/apply-plan-cli.d.ts.map +1 -1
- package/dist/apply-plan-cli.js +432 -15
- package/dist/apply-plan-cli.js.map +1 -1
- package/dist/apply-plan.d.ts +46 -59
- package/dist/apply-plan.d.ts.map +1 -1
- package/dist/apply-plan.js +112 -97
- package/dist/apply-plan.js.map +1 -1
- package/dist/apply-step-fixture.d.ts +87 -0
- package/dist/apply-step-fixture.d.ts.map +1 -0
- package/dist/apply-step-fixture.js +199 -0
- package/dist/apply-step-fixture.js.map +1 -0
- package/dist/apply-store.d.ts +93 -0
- package/dist/apply-store.d.ts.map +1 -0
- package/dist/apply-store.js +625 -0
- package/dist/apply-store.js.map +1 -0
- package/dist/approval-sheet.d.ts +21 -0
- package/dist/approval-sheet.d.ts.map +1 -0
- package/dist/approval-sheet.js +163 -0
- package/dist/approval-sheet.js.map +1 -0
- package/dist/body-command.d.ts +42 -0
- package/dist/body-command.d.ts.map +1 -0
- package/dist/body-command.js +143 -0
- package/dist/body-command.js.map +1 -0
- package/dist/change-set-contract.d.ts +403 -0
- package/dist/change-set-contract.d.ts.map +1 -0
- package/dist/change-set-contract.js +781 -0
- package/dist/change-set-contract.js.map +1 -0
- package/dist/change-set-digest.d.ts +28 -0
- package/dist/change-set-digest.d.ts.map +1 -0
- package/dist/change-set-digest.js +65 -0
- package/dist/change-set-digest.js.map +1 -0
- package/dist/check-cli.d.ts.map +1 -1
- package/dist/check-cli.js +14 -3
- package/dist/check-cli.js.map +1 -1
- package/dist/cli.d.ts +17 -6
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +84 -23
- package/dist/cli.js.map +1 -1
- package/dist/core.d.ts +79 -22
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js +843 -268
- package/dist/core.js.map +1 -1
- package/dist/dry-materialize.d.ts +63 -0
- package/dist/dry-materialize.d.ts.map +1 -0
- package/dist/dry-materialize.js +330 -0
- package/dist/dry-materialize.js.map +1 -0
- package/dist/existing-declaration-adoption.check.d.ts +2 -0
- package/dist/existing-declaration-adoption.check.d.ts.map +1 -0
- package/dist/existing-declaration-adoption.check.js +10 -0
- package/dist/existing-declaration-adoption.check.js.map +1 -0
- package/dist/generated/contract-schema.generated.d.ts +97 -0
- package/dist/generated/contract-schema.generated.d.ts.map +1 -0
- package/dist/generated/contract-schema.generated.js +496 -0
- package/dist/generated/contract-schema.generated.js.map +1 -0
- package/dist/generated/package-scope.generated.d.ts +6 -0
- package/dist/generated/package-scope.generated.d.ts.map +1 -0
- package/dist/generated/package-scope.generated.js +10 -0
- package/dist/generated/package-scope.generated.js.map +1 -0
- package/dist/generated/plan-contracts.generated.d.ts +3 -0
- package/dist/generated/plan-contracts.generated.d.ts.map +1 -0
- package/dist/generated/plan-contracts.generated.js +3101 -0
- package/dist/generated/plan-contracts.generated.js.map +1 -0
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +11 -0
- package/dist/host.js.map +1 -1
- package/dist/identity.d.ts +15 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +48 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +35 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -2
- package/dist/index.js.map +1 -1
- package/dist/inventory-adoption.d.ts +24 -5
- package/dist/inventory-adoption.d.ts.map +1 -1
- package/dist/inventory-adoption.js +70 -25
- package/dist/inventory-adoption.js.map +1 -1
- package/dist/inventory-choice.d.ts +40 -0
- package/dist/inventory-choice.d.ts.map +1 -0
- package/dist/inventory-choice.js +156 -0
- package/dist/inventory-choice.js.map +1 -0
- package/dist/inventory-contract.d.ts +89 -0
- package/dist/inventory-contract.d.ts.map +1 -0
- package/dist/inventory-contract.js +121 -0
- package/dist/inventory-contract.js.map +1 -0
- package/dist/key-editor.d.ts +30 -0
- package/dist/key-editor.d.ts.map +1 -0
- package/dist/key-editor.js +445 -0
- package/dist/key-editor.js.map +1 -0
- package/dist/ledger-contract.d.ts +190 -0
- package/dist/ledger-contract.d.ts.map +1 -0
- package/dist/ledger-contract.js +555 -0
- package/dist/ledger-contract.js.map +1 -0
- package/dist/ledger-trust.d.ts +90 -0
- package/dist/ledger-trust.d.ts.map +1 -0
- package/dist/ledger-trust.js +203 -0
- package/dist/ledger-trust.js.map +1 -0
- package/dist/lockfile-invariants.d.ts +48 -0
- package/dist/lockfile-invariants.d.ts.map +1 -0
- package/dist/lockfile-invariants.js +375 -0
- package/dist/lockfile-invariants.js.map +1 -0
- package/dist/lockfile-readers.d.ts +72 -0
- package/dist/lockfile-readers.d.ts.map +1 -0
- package/dist/lockfile-readers.js +713 -0
- package/dist/lockfile-readers.js.map +1 -0
- package/dist/lockfile-regen.d.ts +106 -0
- package/dist/lockfile-regen.d.ts.map +1 -0
- package/dist/lockfile-regen.js +760 -0
- package/dist/lockfile-regen.js.map +1 -0
- package/dist/lockfile-tool-env.d.ts +29 -0
- package/dist/lockfile-tool-env.d.ts.map +1 -0
- package/dist/lockfile-tool-env.js +111 -0
- package/dist/lockfile-tool-env.js.map +1 -0
- package/dist/materialize.d.ts +113 -0
- package/dist/materialize.d.ts.map +1 -0
- package/dist/materialize.js +881 -0
- package/dist/materialize.js.map +1 -0
- package/dist/observe-repository.d.ts +90 -0
- package/dist/observe-repository.d.ts.map +1 -0
- package/dist/observe-repository.js +1367 -0
- package/dist/observe-repository.js.map +1 -0
- package/dist/plan-bundle-setup-fixture.d.ts +68 -0
- package/dist/plan-bundle-setup-fixture.d.ts.map +1 -0
- package/dist/plan-bundle-setup-fixture.js +167 -0
- package/dist/plan-bundle-setup-fixture.js.map +1 -0
- package/dist/plan-bundle.d.ts +256 -0
- package/dist/plan-bundle.d.ts.map +1 -0
- package/dist/plan-bundle.js +882 -0
- package/dist/plan-bundle.js.map +1 -0
- package/dist/plan-command.d.ts +29 -0
- package/dist/plan-command.d.ts.map +1 -0
- package/dist/plan-command.js +523 -0
- package/dist/plan-command.js.map +1 -0
- package/dist/plan-contract.d.ts +153 -0
- package/dist/plan-contract.d.ts.map +1 -0
- package/dist/plan-contract.js +61 -0
- package/dist/plan-contract.js.map +1 -0
- package/dist/plan-digest.d.ts +25 -0
- package/dist/plan-digest.d.ts.map +1 -0
- package/dist/plan-digest.js +106 -0
- package/dist/plan-digest.js.map +1 -0
- package/dist/plan-rules.d.ts +23 -0
- package/dist/plan-rules.d.ts.map +1 -0
- package/dist/plan-rules.js +177 -0
- package/dist/plan-rules.js.map +1 -0
- package/dist/planned-bundle.d.ts +20 -0
- package/dist/planned-bundle.d.ts.map +1 -0
- package/dist/planned-bundle.js +191 -0
- package/dist/planned-bundle.js.map +1 -0
- package/dist/product-repository.d.ts +4 -0
- package/dist/product-repository.d.ts.map +1 -1
- package/dist/product-repository.js +9 -1
- package/dist/product-repository.js.map +1 -1
- package/dist/provenance-gate.d.ts +48 -0
- package/dist/provenance-gate.d.ts.map +1 -0
- package/dist/provenance-gate.js +324 -0
- package/dist/provenance-gate.js.map +1 -0
- package/dist/pull-request-body.d.ts +45 -0
- package/dist/pull-request-body.d.ts.map +1 -0
- package/dist/pull-request-body.js +232 -0
- package/dist/pull-request-body.js.map +1 -0
- package/dist/registry-snapshot.d.ts +141 -0
- package/dist/registry-snapshot.d.ts.map +1 -0
- package/dist/registry-snapshot.js +483 -0
- package/dist/registry-snapshot.js.map +1 -0
- package/dist/release-age-edit.d.ts +52 -0
- package/dist/release-age-edit.d.ts.map +1 -0
- package/dist/release-age-edit.js +413 -0
- package/dist/release-age-edit.js.map +1 -0
- package/dist/root-entries.d.ts +36 -0
- package/dist/root-entries.d.ts.map +1 -0
- package/dist/root-entries.js +80 -0
- package/dist/root-entries.js.map +1 -0
- package/dist/setup-template-scripts.d.ts +36 -0
- package/dist/setup-template-scripts.d.ts.map +1 -0
- package/dist/setup-template-scripts.js +568 -0
- package/dist/setup-template-scripts.js.map +1 -0
- package/dist/setup-templates.d.ts +55 -0
- package/dist/setup-templates.d.ts.map +1 -0
- package/dist/setup-templates.js +438 -0
- package/dist/setup-templates.js.map +1 -0
- package/dist/skills.d.ts +34 -1
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +129 -17
- package/dist/skills.js.map +1 -1
- package/dist/status.d.ts +63 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +539 -0
- package/dist/status.js.map +1 -0
- package/dist/types.d.ts +151 -13
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/skeleton/README.md +14 -9
- package/skeleton/package.json +2 -1
- package/skill/SKILL.md +23 -7
- package/skill-catalogue/advisor/SKILL.md +59 -6
- package/skill-catalogue/architect/SKILL.md +2 -2
- package/skill-catalogue/bouncer/SKILL.md +2 -2
- package/skill-catalogue/builder/SKILL.md +2 -2
- package/skill-catalogue/butler/SKILL.md +2 -2
- package/skill-catalogue/controller/SKILL.md +2 -2
- package/skill-catalogue/customer/SKILL.md +2 -2
- package/skill-catalogue/designer/SKILL.md +4 -2
- package/skill-catalogue/giver/SKILL.md +2 -2
- package/skill-catalogue/influencer/SKILL.md +2 -2
- package/skill-catalogue/inspector/SKILL.md +2 -2
- package/skill-catalogue/integrator/SKILL.md +2 -2
- package/skill-catalogue/keeper/SKILL.md +2 -2
- package/skill-catalogue/launcher/SKILL.md +23 -7
- package/skill-catalogue/locksmith/SKILL.md +2 -2
- package/skill-catalogue/messenger/SKILL.md +2 -2
- package/skill-catalogue/observer/SKILL.md +2 -2
- package/skill-catalogue/publisher/SKILL.md +2 -2
- package/skill-catalogue/starter/SKILL.md +3 -2
- package/skill-catalogue/strategist/SKILL.md +12 -4
- package/skill-catalogue/writer/SKILL.md +2 -2
- package/src/admission-fixture.ts +585 -0
- package/src/admission.ts +819 -0
- package/src/agents-guide.ts +29 -0
- package/src/apply-command-options.check.ts +27 -0
- package/src/apply-plan-cli.ts +454 -14
- package/src/apply-plan.ts +112 -124
- package/src/apply-step-fixture.ts +236 -0
- package/src/apply-store.ts +584 -0
- package/src/approval-sheet.ts +170 -0
- package/src/body-command.ts +162 -0
- package/src/change-set-contract.ts +987 -0
- package/src/change-set-digest.ts +70 -0
- package/src/check-cli.ts +14 -3
- package/src/cli.ts +90 -22
- package/src/core.ts +973 -275
- package/src/dry-materialize.ts +353 -0
- package/src/existing-declaration-adoption.check.ts +12 -0
- package/src/generated/contract-schema.generated.ts +520 -0
- package/src/generated/package-scope.generated.ts +10 -0
- package/src/generated/plan-contracts.generated.ts +3101 -0
- package/src/host.ts +10 -0
- package/src/identity.ts +51 -0
- package/src/index.ts +74 -3
- package/src/inventory-adoption.ts +107 -29
- package/src/inventory-choice.ts +172 -0
- package/src/inventory-contract.ts +166 -0
- package/src/key-editor.ts +446 -0
- package/src/ledger-contract.ts +660 -0
- package/src/ledger-trust.ts +272 -0
- package/src/lockfile-invariants.ts +421 -0
- package/src/lockfile-readers.ts +749 -0
- package/src/lockfile-regen.ts +851 -0
- package/src/lockfile-tool-env.ts +131 -0
- package/src/materialize.ts +915 -0
- package/src/observe-repository.ts +1365 -0
- package/src/plan-bundle-setup-fixture.ts +200 -0
- package/src/plan-bundle.ts +1014 -0
- package/src/plan-command.ts +532 -0
- package/src/plan-contract.ts +179 -0
- package/src/plan-digest.ts +102 -0
- package/src/plan-rules.ts +188 -0
- package/src/planned-bundle.ts +211 -0
- package/src/product-repository.ts +10 -1
- package/src/provenance-gate.ts +352 -0
- package/src/pull-request-body.ts +261 -0
- package/src/registry-snapshot.ts +534 -0
- package/src/release-age-edit.ts +430 -0
- package/src/root-entries.ts +81 -0
- package/src/setup-template-scripts.ts +580 -0
- package/src/setup-templates.ts +479 -0
- package/src/skills.ts +161 -18
- package/src/status.ts +557 -0
- package/src/types.ts +148 -13
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure renderers for the setup template acts' script and workflow files.
|
|
3
|
+
*
|
|
4
|
+
* Each renderer returns exact file bytes; path scope optionally takes the
|
|
5
|
+
* supported authoring agent provenance. With no input it preserves legacy
|
|
6
|
+
* bytes. Rendering is pure, so
|
|
7
|
+
* the text is a pure function of this module alone: no clock, no randomness,
|
|
8
|
+
* no filesystem, no environment. Everything that reads a clock or a
|
|
9
|
+
* repository does so inside the emitted script, when a workflow runs it, and
|
|
10
|
+
* never here.
|
|
11
|
+
*
|
|
12
|
+
* Files rendered here:
|
|
13
|
+
* - `.github/scripts/clossys-collect-adoption-snapshot.mjs`
|
|
14
|
+
* - `.github/workflows/clossys-adoption-evidence.yml`
|
|
15
|
+
* - `.github/workflows/clossys-path-scope.yml`
|
|
16
|
+
*
|
|
17
|
+
* The path-scope script is standalone, so it runs the same under a test as it
|
|
18
|
+
* does inside the workflow that embeds it.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* The path patterns an apply pull request may own, in the change-set
|
|
22
|
+
* contract's order. The path-scope script embeds this list.
|
|
23
|
+
*/
|
|
24
|
+
export const OWNED_PATH_PATTERNS = [
|
|
25
|
+
"clossys/**",
|
|
26
|
+
".agents/skills/clossys-*/**",
|
|
27
|
+
".claude/skills/clossys-*",
|
|
28
|
+
".cursor/skills/clossys-*",
|
|
29
|
+
"AGENTS.md",
|
|
30
|
+
"CLAUDE.md",
|
|
31
|
+
".starter/request.json",
|
|
32
|
+
".github/workflows/clossys-*",
|
|
33
|
+
".github/scripts/clossys-*",
|
|
34
|
+
"package.json",
|
|
35
|
+
"package-lock.json",
|
|
36
|
+
"pnpm-lock.yaml",
|
|
37
|
+
"yarn.lock",
|
|
38
|
+
".yarnrc.yml",
|
|
39
|
+
"pnpm-workspace.yaml",
|
|
40
|
+
"**/repository-profile.json",
|
|
41
|
+
"**/repository-declaration.json",
|
|
42
|
+
];
|
|
43
|
+
const CHECKOUT = "actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0";
|
|
44
|
+
const UPLOAD_ARTIFACT = "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7";
|
|
45
|
+
const HEREDOC_TERMINATOR = "CLOSSYS_PATH_SCOPE";
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// The snapshot collector script
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// The emitted scripts' module import lines are kept as quoted strings so that no
|
|
50
|
+
// line of this module's own source begins with an import statement.
|
|
51
|
+
const COLLECTOR_IMPORTS = [
|
|
52
|
+
'import { createHash } from "node:crypto";',
|
|
53
|
+
'import { closeSync, constants, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";',
|
|
54
|
+
'import { dirname, resolve, sep } from "node:path";',
|
|
55
|
+
].join("\n");
|
|
56
|
+
const PATH_SCOPE_IMPORTS = 'import { execFileSync } from "node:child_process";';
|
|
57
|
+
const SNAPSHOT_COLLECTOR = String.raw `// Clossys adoption snapshot collector.
|
|
58
|
+
//
|
|
59
|
+
// This script is consumer-owned: it lives in this repository, and you review
|
|
60
|
+
// and change it like any other code here. It is uncredentialed. It reads no
|
|
61
|
+
// package or registry secret, installs nothing, and runs no package, Advisor
|
|
62
|
+
// or target command. The trusted decision job never reads or trusts what this
|
|
63
|
+
// script produces, because a pull request controls the workflow that runs it
|
|
64
|
+
// and can change this file.
|
|
65
|
+
//
|
|
66
|
+
// Usage: node clossys-collect-adoption-snapshot.mjs <request.json> <output-dir>
|
|
67
|
+
//
|
|
68
|
+
// From the request it takes snapshot.repository, evidence.assessment and
|
|
69
|
+
// evidence.targetInput. It copies exactly those two files, found relative to
|
|
70
|
+
// the working directory (the repository root), into <output-dir>, and writes
|
|
71
|
+
// <output-dir>/snapshot.json with the pull request facts from the environment
|
|
72
|
+
// (REPOSITORY, PR_NUMBER, BASE_SHA, HEAD_SHA, RUN_ID) and each file's size and
|
|
73
|
+
// SHA-256.
|
|
74
|
+
//
|
|
75
|
+
// It refuses, with exit code 2 and one line on stderr, a request or an
|
|
76
|
+
// environment value it cannot vouch for; an evidence path that is not a
|
|
77
|
+
// normalized relative path, is missing, is a symlink, is not a regular file, or
|
|
78
|
+
// is larger than 524288 bytes; and an output directory that already holds
|
|
79
|
+
// anything. A refusal leaves nothing half-written.
|
|
80
|
+
${COLLECTOR_IMPORTS}
|
|
81
|
+
|
|
82
|
+
const NAME = "clossys-collect-adoption-snapshot";
|
|
83
|
+
const MAX_BYTES = 524288;
|
|
84
|
+
const COMMIT = /^[0-9a-f]{40}$/;
|
|
85
|
+
|
|
86
|
+
class Refusal extends Error {}
|
|
87
|
+
|
|
88
|
+
function refuse(message) {
|
|
89
|
+
throw new Refusal(message);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function show(text) {
|
|
93
|
+
return String(text).replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/g, "?").slice(0, 200);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function env(name) {
|
|
97
|
+
const value = process.env[name];
|
|
98
|
+
return typeof value === "string" ? value : "";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function isRecord(value) {
|
|
102
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function isNormalizedRelativePath(value) {
|
|
106
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 240) return false;
|
|
107
|
+
if (value.includes("\\") || value.startsWith("/") || /^[A-Za-z]:/.test(value)) return false;
|
|
108
|
+
if (/[\u0000-\u001f\u007f]/.test(value)) return false;
|
|
109
|
+
return value.split("/").every((part) => part !== "" && part !== "." && part !== "..");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function sha256(data) {
|
|
113
|
+
return createHash("sha256").update(data).digest("hex");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function readBounded(path, label) {
|
|
117
|
+
let link;
|
|
118
|
+
try {
|
|
119
|
+
link = lstatSync(path);
|
|
120
|
+
} catch {
|
|
121
|
+
refuse(label + " is missing or unreadable");
|
|
122
|
+
}
|
|
123
|
+
if (link.isSymbolicLink() || !link.isFile()) refuse(label + " is not a regular non-symlink file");
|
|
124
|
+
if (link.size > MAX_BYTES) refuse(label + " is larger than " + MAX_BYTES + " bytes");
|
|
125
|
+
let fd;
|
|
126
|
+
try {
|
|
127
|
+
fd = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW || 0));
|
|
128
|
+
} catch {
|
|
129
|
+
refuse(label + " cannot be opened");
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const stat = fstatSync(fd);
|
|
133
|
+
if (!stat.isFile() || stat.size > MAX_BYTES) refuse(label + " is not a bounded regular file");
|
|
134
|
+
const content = readFileSync(fd);
|
|
135
|
+
if (content.length > MAX_BYTES) refuse(label + " is larger than " + MAX_BYTES + " bytes");
|
|
136
|
+
return content;
|
|
137
|
+
} finally {
|
|
138
|
+
closeSync(fd);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function readEvidence(root, path) {
|
|
143
|
+
const label = "evidence file " + show(path);
|
|
144
|
+
const candidate = resolve(root, path);
|
|
145
|
+
if (!candidate.startsWith(root + sep)) refuse(label + " is outside the working directory");
|
|
146
|
+
let real;
|
|
147
|
+
try {
|
|
148
|
+
real = realpathSync(candidate);
|
|
149
|
+
} catch {
|
|
150
|
+
refuse(label + " is missing or unreadable");
|
|
151
|
+
}
|
|
152
|
+
if (real !== candidate) refuse(label + " passes through a symbolic link");
|
|
153
|
+
return readBounded(candidate, label);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function readRequest(requestArgument) {
|
|
157
|
+
const content = readBounded(resolve(requestArgument), "request file");
|
|
158
|
+
let request;
|
|
159
|
+
try {
|
|
160
|
+
request = JSON.parse(content.toString("utf8"));
|
|
161
|
+
} catch {
|
|
162
|
+
refuse("the request is not valid JSON");
|
|
163
|
+
}
|
|
164
|
+
if (!isRecord(request) || !isRecord(request.snapshot) || typeof request.snapshot.repository !== "string" || request.snapshot.repository === "") {
|
|
165
|
+
refuse("the request has no snapshot.repository");
|
|
166
|
+
}
|
|
167
|
+
if (!isRecord(request.evidence)) refuse("the request has no evidence paths");
|
|
168
|
+
const assessment = request.evidence.assessment;
|
|
169
|
+
const targetInput = request.evidence.targetInput;
|
|
170
|
+
if (!isNormalizedRelativePath(assessment)) refuse("the request's evidence.assessment is not a normalized relative path");
|
|
171
|
+
if (!isNormalizedRelativePath(targetInput)) refuse("the request's evidence.targetInput is not a normalized relative path");
|
|
172
|
+
if (assessment === targetInput) refuse("the request names the same evidence path twice; the two must be distinct");
|
|
173
|
+
for (const path of [assessment, targetInput]) {
|
|
174
|
+
if (path.toLowerCase() === "snapshot.json") refuse("the request's evidence path " + show(path) + " collides with snapshot.json");
|
|
175
|
+
}
|
|
176
|
+
return { repository: request.snapshot.repository, paths: [assessment, targetInput] };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function readEnvironment(repository) {
|
|
180
|
+
if (env("REPOSITORY") !== repository) refuse("REPOSITORY does not equal the request's snapshot.repository");
|
|
181
|
+
const pullRequest = env("PR_NUMBER");
|
|
182
|
+
if (!/^[1-9][0-9]*$/.test(pullRequest) || !Number.isSafeInteger(Number(pullRequest))) refuse("PR_NUMBER is not a positive safe integer");
|
|
183
|
+
const baseSha = env("BASE_SHA");
|
|
184
|
+
if (!COMMIT.test(baseSha)) refuse("BASE_SHA is not a 40-character lowercase hex commit");
|
|
185
|
+
const headSha = env("HEAD_SHA");
|
|
186
|
+
if (!COMMIT.test(headSha)) refuse("HEAD_SHA is not a 40-character lowercase hex commit");
|
|
187
|
+
const runId = env("RUN_ID");
|
|
188
|
+
if (!/^[0-9]+$/.test(runId)) refuse("RUN_ID is not a non-empty run of digits");
|
|
189
|
+
return { pullRequestNumber: Number(pullRequest), baseSha, headSha, runId };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function checkOutputDirectory(out) {
|
|
193
|
+
let stat = null;
|
|
194
|
+
try {
|
|
195
|
+
stat = lstatSync(out);
|
|
196
|
+
} catch (error) {
|
|
197
|
+
if (!error || error.code !== "ENOENT") refuse("the output directory cannot be inspected");
|
|
198
|
+
}
|
|
199
|
+
if (stat === null) return;
|
|
200
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) refuse("the output path exists and is not a directory");
|
|
201
|
+
if (readdirSync(out).length > 0) refuse("the output directory already exists and is not empty");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function write(out, files, manifestText) {
|
|
205
|
+
let firstCreated;
|
|
206
|
+
try {
|
|
207
|
+
firstCreated = mkdirSync(out, { recursive: true });
|
|
208
|
+
for (const file of files) {
|
|
209
|
+
const target = resolve(out, file.path);
|
|
210
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
211
|
+
writeFileSync(target, file.content, { flag: "wx" });
|
|
212
|
+
}
|
|
213
|
+
writeFileSync(resolve(out, "snapshot.json"), manifestText, { flag: "wx" });
|
|
214
|
+
} catch {
|
|
215
|
+
try {
|
|
216
|
+
if (firstCreated !== undefined) rmSync(firstCreated, { recursive: true, force: true });
|
|
217
|
+
else for (const name of readdirSync(out)) rmSync(resolve(out, name), { recursive: true, force: true });
|
|
218
|
+
} catch {
|
|
219
|
+
// Nothing more can be done; the refusal below still reports the failure.
|
|
220
|
+
}
|
|
221
|
+
refuse("the snapshot could not be written");
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function main() {
|
|
226
|
+
const args = process.argv.slice(2);
|
|
227
|
+
if (args.length !== 2 || args[0] === "" || args[1] === "") refuse("usage: node clossys-collect-adoption-snapshot.mjs <request.json> <output-dir>");
|
|
228
|
+
const out = resolve(args[1]);
|
|
229
|
+
const request = readRequest(args[0]);
|
|
230
|
+
const facts = readEnvironment(request.repository);
|
|
231
|
+
checkOutputDirectory(out);
|
|
232
|
+
|
|
233
|
+
const root = realpathSync(process.cwd());
|
|
234
|
+
const files = request.paths.map((path) => ({ path, content: readEvidence(root, path) }));
|
|
235
|
+
files.sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));
|
|
236
|
+
|
|
237
|
+
const before = {
|
|
238
|
+
schemaVersion: 1,
|
|
239
|
+
provider: "github-actions",
|
|
240
|
+
eventName: "pull_request",
|
|
241
|
+
repository: request.repository,
|
|
242
|
+
pullRequestNumber: facts.pullRequestNumber,
|
|
243
|
+
baseSha: facts.baseSha,
|
|
244
|
+
headSha: facts.headSha,
|
|
245
|
+
workflowRunId: facts.runId,
|
|
246
|
+
artifactName: "adoption-snapshot-" + facts.runId,
|
|
247
|
+
};
|
|
248
|
+
const capturedAt = new Date().toISOString();
|
|
249
|
+
const listed = files.map((file) => ({ path: file.path, size: file.content.length, sha256: sha256(file.content) }));
|
|
250
|
+
const digest = sha256(Buffer.from(JSON.stringify({ ...before, capturedAt, files: listed }, null, 2), "utf8"));
|
|
251
|
+
const manifest = { ...before, digest, capturedAt, files: listed };
|
|
252
|
+
|
|
253
|
+
write(out, files, JSON.stringify(manifest, null, 2) + "\n");
|
|
254
|
+
console.log(NAME + ": wrote " + files.length + " evidence file(s) and snapshot.json");
|
|
255
|
+
return 0;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
process.exitCode = main();
|
|
260
|
+
} catch (error) {
|
|
261
|
+
console.error(NAME + ": " + (error instanceof Refusal ? show(error.message) : "unexpected failure"));
|
|
262
|
+
process.exitCode = 2;
|
|
263
|
+
}
|
|
264
|
+
`;
|
|
265
|
+
/** The bytes of `.github/scripts/clossys-collect-adoption-snapshot.mjs`. */
|
|
266
|
+
export function renderSnapshotCollector() {
|
|
267
|
+
return SNAPSHOT_COLLECTOR;
|
|
268
|
+
}
|
|
269
|
+
// ---------------------------------------------------------------------------
|
|
270
|
+
// The adoption evidence workflow
|
|
271
|
+
// ---------------------------------------------------------------------------
|
|
272
|
+
const ADOPTION_EVIDENCE_WORKFLOW = [
|
|
273
|
+
"# Clossys adoption evidence, written by Clossys Launcher.",
|
|
274
|
+
"#",
|
|
275
|
+
"# This is the uncredentialed half of the adoption check. It runs on",
|
|
276
|
+
"# pull_request, checks out the pull request head with no persisted credential,",
|
|
277
|
+
"# and uploads a small snapshot of two evidence files as an artifact. It holds no",
|
|
278
|
+
"# secret, installs nothing, and runs no package, Advisor or target command.",
|
|
279
|
+
"#",
|
|
280
|
+
"# The artifact this workflow uploads is NOT read by the trusted decision job.",
|
|
281
|
+
"# A pull request controls this workflow and can edit it, so what it produces is",
|
|
282
|
+
"# not evidence the decision trusts.",
|
|
283
|
+
"#",
|
|
284
|
+
"# The collector is a plain script in this repository:",
|
|
285
|
+
"# .github/scripts/clossys-collect-adoption-snapshot.mjs. It reads the two",
|
|
286
|
+
"# evidence paths named by .starter/request.json and copies exactly those files.",
|
|
287
|
+
"name: Clossys adoption evidence",
|
|
288
|
+
"",
|
|
289
|
+
"on:",
|
|
290
|
+
" pull_request:",
|
|
291
|
+
" types: [opened, synchronize, reopened]",
|
|
292
|
+
"",
|
|
293
|
+
"permissions:",
|
|
294
|
+
" contents: read",
|
|
295
|
+
"",
|
|
296
|
+
"jobs:",
|
|
297
|
+
" collect-adoption-evidence:",
|
|
298
|
+
" runs-on: ubuntu-latest",
|
|
299
|
+
" timeout-minutes: 10",
|
|
300
|
+
" steps:",
|
|
301
|
+
" - name: Check out the pull request head",
|
|
302
|
+
` uses: ${CHECKOUT}`,
|
|
303
|
+
" with:",
|
|
304
|
+
" ref: ${{ github.event.pull_request.head.sha }}",
|
|
305
|
+
" persist-credentials: false",
|
|
306
|
+
"",
|
|
307
|
+
" # Values reach the script only through the environment, never through its",
|
|
308
|
+
" # text. The script runs no package, Advisor or target command.",
|
|
309
|
+
" - name: Collect the snapshot",
|
|
310
|
+
" env:",
|
|
311
|
+
" REPOSITORY: ${{ github.repository }}",
|
|
312
|
+
" PR_NUMBER: ${{ github.event.pull_request.number }}",
|
|
313
|
+
" BASE_SHA: ${{ github.event.pull_request.base.sha }}",
|
|
314
|
+
" HEAD_SHA: ${{ github.event.pull_request.head.sha }}",
|
|
315
|
+
" RUN_ID: ${{ github.run_id }}",
|
|
316
|
+
" run: |",
|
|
317
|
+
" node .github/scripts/clossys-collect-adoption-snapshot.mjs \\",
|
|
318
|
+
" .starter/request.json \\",
|
|
319
|
+
' "$RUNNER_TEMP/adoption-snapshot"',
|
|
320
|
+
"",
|
|
321
|
+
" - name: Upload the snapshot",
|
|
322
|
+
` uses: ${UPLOAD_ARTIFACT}`,
|
|
323
|
+
" with:",
|
|
324
|
+
" name: adoption-snapshot-${{ github.run_id }}",
|
|
325
|
+
" path: ${{ runner.temp }}/adoption-snapshot",
|
|
326
|
+
" if-no-files-found: error",
|
|
327
|
+
" retention-days: 7",
|
|
328
|
+
"",
|
|
329
|
+
].join("\n");
|
|
330
|
+
/** The bytes of `.github/workflows/clossys-adoption-evidence.yml`. */
|
|
331
|
+
export function renderAdoptionEvidenceWorkflow() {
|
|
332
|
+
return ADOPTION_EVIDENCE_WORKFLOW;
|
|
333
|
+
}
|
|
334
|
+
// ---------------------------------------------------------------------------
|
|
335
|
+
// The path-scope script and workflow
|
|
336
|
+
// ---------------------------------------------------------------------------
|
|
337
|
+
const PATH_SCOPE_SCRIPT = String.raw `// Clossys path scope.
|
|
338
|
+
//
|
|
339
|
+
// Fails a Clossys apply pull request that changes a file Clossys does not own,
|
|
340
|
+
// or an owned file the pull request's own ledger does not name. It reads git
|
|
341
|
+
// only: git diff and git show, run without a shell. It never runs, loads or
|
|
342
|
+
// evaluates anything from the checked-out files.
|
|
343
|
+
//
|
|
344
|
+
// Inputs come from the environment only: HEAD_REF (the pull request head
|
|
345
|
+
// branch), BASE_SHA and HEAD_SHA (40 lowercase hex commits). Exit codes: 0
|
|
346
|
+
// passed (or not an apply pull request), 1 findings, 2 could not check. Every
|
|
347
|
+
// doubt is a 2.
|
|
348
|
+
${PATH_SCOPE_IMPORTS}
|
|
349
|
+
|
|
350
|
+
const APPLY_PREFIX = "clossys/apply-";
|
|
351
|
+
const LEDGER_PATH = "clossys/.state/installed.json";
|
|
352
|
+
const EXEMPT = new Set([LEDGER_PATH, "package-lock.json", "pnpm-lock.yaml", "package.json"]);
|
|
353
|
+
const OWNED = ${JSON.stringify(OWNED_PATH_PATTERNS, null, 2)};
|
|
354
|
+
const MAX_PRINTED = 50;
|
|
355
|
+
const MAX_BUFFER = 64 * 1024 * 1024;
|
|
356
|
+
const COMMIT = /^[0-9a-f]{40}$/;
|
|
357
|
+
const DECODER = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
358
|
+
|
|
359
|
+
// matcher:begin
|
|
360
|
+
const SAFE_PATH = /^(?:(?!\.\.?\/)[^/\\\u0000-\u001f]+\/)*(?!\.\.?$)[^/\\\u0000-\u001f]+$/u;
|
|
361
|
+
|
|
362
|
+
function isSafePath(path) {
|
|
363
|
+
return SAFE_PATH.test(path);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function isPattern(pattern) {
|
|
367
|
+
if (pattern === "**" || !isSafePath(pattern)) return false;
|
|
368
|
+
return pattern.split("/").every((segment) => segment === "**" || !segment.includes("**"));
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function segmentMatches(segment, glob) {
|
|
372
|
+
const source = glob.split("*").map((part) => part.replace(/[.+?^$\{}()|[\]\\]/g, "\\$&")).join("[^/]*");
|
|
373
|
+
return new RegExp("^" + source + "$", "u").test(segment);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function segmentsMatch(path, pattern) {
|
|
377
|
+
if (pattern.length === 0) return path.length === 0;
|
|
378
|
+
const head = pattern[0];
|
|
379
|
+
const rest = pattern.slice(1);
|
|
380
|
+
if (head === "**") {
|
|
381
|
+
for (let skip = 0; skip <= path.length; skip += 1) if (segmentsMatch(path.slice(skip), rest)) return true;
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
return path.length > 0 && segmentMatches(path[0], head) && segmentsMatch(path.slice(1), rest);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function matches(path, pattern) {
|
|
388
|
+
if (!isSafePath(path) || !isPattern(pattern)) return false;
|
|
389
|
+
return segmentsMatch(path.split("/"), pattern.split("/"));
|
|
390
|
+
}
|
|
391
|
+
// matcher:end
|
|
392
|
+
|
|
393
|
+
class Refusal extends Error {}
|
|
394
|
+
|
|
395
|
+
function refuse(message) {
|
|
396
|
+
throw new Refusal(message);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function clean(text) {
|
|
400
|
+
return String(text).replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/g, "?").slice(0, 300);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function isRecord(value) {
|
|
404
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function gitOutput(args, what) {
|
|
408
|
+
try {
|
|
409
|
+
return execFileSync("git", args, { maxBuffer: MAX_BUFFER, stdio: ["ignore", "pipe", "ignore"] });
|
|
410
|
+
} catch {
|
|
411
|
+
return refuse(what + " failed");
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function decode(buffer, what) {
|
|
416
|
+
try {
|
|
417
|
+
return DECODER.decode(buffer);
|
|
418
|
+
} catch {
|
|
419
|
+
return refuse(what + " is not valid UTF-8");
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function changedPaths(baseSha, headSha) {
|
|
424
|
+
const output = gitOutput(
|
|
425
|
+
["diff", "--name-only", "-z", "--no-renames", "--no-ext-diff", "--diff-filter=ACDMRTUXB", baseSha + "..." + headSha],
|
|
426
|
+
"git diff",
|
|
427
|
+
);
|
|
428
|
+
const parts = decode(output, "the changed path list").split("\u0000");
|
|
429
|
+
if (parts.pop() !== "") refuse("the changed path list is not NUL-terminated");
|
|
430
|
+
return parts;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function namedPaths(headSha) {
|
|
434
|
+
const text = decode(gitOutput(["show", "--no-textconv", headSha + ":" + LEDGER_PATH], "reading the head ledger"), "the head ledger");
|
|
435
|
+
let ledger;
|
|
436
|
+
try {
|
|
437
|
+
ledger = JSON.parse(text);
|
|
438
|
+
} catch {
|
|
439
|
+
return refuse("the head ledger is not valid JSON");
|
|
440
|
+
}
|
|
441
|
+
if (!isRecord(ledger) || !Array.isArray(ledger.files)) refuse("the head ledger has no files array");
|
|
442
|
+
const named = new Set();
|
|
443
|
+
for (const row of ledger.files) {
|
|
444
|
+
if (!isRecord(row) || typeof row.path !== "string") refuse("a row in the head ledger's files has no string path");
|
|
445
|
+
named.add(row.path);
|
|
446
|
+
}
|
|
447
|
+
for (const member of ["entries", "keys"]) {
|
|
448
|
+
if (!Object.hasOwn(ledger, member)) continue;
|
|
449
|
+
if (!Array.isArray(ledger[member])) refuse("the head ledger's " + member + " is not an array");
|
|
450
|
+
for (const row of ledger[member]) {
|
|
451
|
+
if (!isRecord(row) || typeof row.file !== "string") refuse("a row in the head ledger's " + member + " has no string file");
|
|
452
|
+
named.add(row.file);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return named;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function main() {
|
|
459
|
+
const headRef = process.env.HEAD_REF;
|
|
460
|
+
if (typeof headRef !== "string" || headRef === "") refuse("HEAD_REF is not set");
|
|
461
|
+
if (!headRef.startsWith(APPLY_PREFIX)) {
|
|
462
|
+
console.log("path-scope: not a Clossys apply pull request (the head branch does not start with " + APPLY_PREFIX + "); nothing to check");
|
|
463
|
+
return 0;
|
|
464
|
+
}
|
|
465
|
+
const baseSha = process.env.BASE_SHA;
|
|
466
|
+
const headSha = process.env.HEAD_SHA;
|
|
467
|
+
if (typeof baseSha !== "string" || !COMMIT.test(baseSha)) refuse("BASE_SHA is not a 40-character lowercase hex commit");
|
|
468
|
+
if (typeof headSha !== "string" || !COMMIT.test(headSha)) refuse("HEAD_SHA is not a 40-character lowercase hex commit");
|
|
469
|
+
|
|
470
|
+
const changed = changedPaths(baseSha, headSha);
|
|
471
|
+
const named = namedPaths(headSha);
|
|
472
|
+
const findings = [];
|
|
473
|
+
for (const path of changed) {
|
|
474
|
+
if (!OWNED.some((pattern) => matches(path, pattern))) findings.push("outside-owned-scope: " + clean(path));
|
|
475
|
+
if (!EXEMPT.has(path) && !named.has(path)) findings.push("not-named-by-ledger: " + clean(path));
|
|
476
|
+
}
|
|
477
|
+
if (findings.length === 0) {
|
|
478
|
+
console.log("path-scope: " + changed.length + " path(s) checked");
|
|
479
|
+
return 0;
|
|
480
|
+
}
|
|
481
|
+
for (const finding of findings.slice(0, MAX_PRINTED)) console.error(finding);
|
|
482
|
+
const shown = Math.min(findings.length, MAX_PRINTED);
|
|
483
|
+
console.error("path-scope: " + findings.length + " finding(s) in " + changed.length + " changed path(s)" + (shown < findings.length ? "; first " + shown + " shown" : ""));
|
|
484
|
+
return 1;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
try {
|
|
488
|
+
process.exitCode = main();
|
|
489
|
+
} catch (error) {
|
|
490
|
+
console.error("path-scope: refused: " + (error instanceof Refusal ? error.message : "unexpected failure"));
|
|
491
|
+
process.exitCode = 2;
|
|
492
|
+
}
|
|
493
|
+
`;
|
|
494
|
+
/**
|
|
495
|
+
* The standalone path-scope script (its text ends in one LF). The
|
|
496
|
+
* path-scope workflow embeds it; a test runs it with `node` directly.
|
|
497
|
+
*/
|
|
498
|
+
export function renderPathScopeScript(agentProvenance = undefined) {
|
|
499
|
+
if (agentProvenance === undefined)
|
|
500
|
+
return PATH_SCOPE_SCRIPT;
|
|
501
|
+
if (!["codex", "claude", "cursor"].includes(agentProvenance))
|
|
502
|
+
throw new TypeError("unsupported agent provenance");
|
|
503
|
+
// Keep legacy bytes unchanged. Explicit provenance opts into the expanded
|
|
504
|
+
// branch recognizer; every supported namespace still runs the full checks.
|
|
505
|
+
return PATH_SCOPE_SCRIPT
|
|
506
|
+
.replace('const APPLY_PREFIX = "clossys/apply-";', 'const APPLY_PREFIX = "a supported apply branch";\nconst APPLY_BRANCH = /^(clossys|codex|claude|cursor)\\/apply-[0-9a-f]{12}$/;')
|
|
507
|
+
.replace('if (!headRef.startsWith(APPLY_PREFIX)) {', 'if (!APPLY_BRANCH.test(headRef)) {\n if (headRef.includes("/apply")) refuse("unsupported or malformed apply branch");');
|
|
508
|
+
}
|
|
509
|
+
const PATH_SCOPE_WORKFLOW_HEAD = [
|
|
510
|
+
"# Clossys path scope, written by Clossys Launcher.",
|
|
511
|
+
"#",
|
|
512
|
+
"# This applies to pull requests whose head branch starts with clossys/apply-.",
|
|
513
|
+
"# For those it lists every file the pull request changes, deletions included,",
|
|
514
|
+
"# and fails unless each one matches a path Clossys may own and, apart from the",
|
|
515
|
+
"# ledger, package.json and the lockfiles, is named by the pull request's own",
|
|
516
|
+
"# clossys/.state/installed.json. Any other pull request passes with a notice.",
|
|
517
|
+
"# That decision is made inside the script, not by a job condition, so this job",
|
|
518
|
+
"# and its check always run.",
|
|
519
|
+
"#",
|
|
520
|
+
"# This workflow runs in the pull request's own context, so a pull request can",
|
|
521
|
+
"# edit it. It catches an agent's mistakes, not a hostile author. The trusted",
|
|
522
|
+
"# admission job is the separate workflow_run workflow, which runs from the",
|
|
523
|
+
"# protected base.",
|
|
524
|
+
"#",
|
|
525
|
+
"# The script only runs git diff and git show. It never runs, loads or evaluates",
|
|
526
|
+
"# anything from the checked-out files, and the branch name reaches it only",
|
|
527
|
+
"# through the environment.",
|
|
528
|
+
"name: Clossys path scope",
|
|
529
|
+
"",
|
|
530
|
+
"on:",
|
|
531
|
+
" pull_request:",
|
|
532
|
+
" types: [opened, synchronize, reopened]",
|
|
533
|
+
"",
|
|
534
|
+
"permissions:",
|
|
535
|
+
" contents: read",
|
|
536
|
+
"",
|
|
537
|
+
"jobs:",
|
|
538
|
+
" path-scope:",
|
|
539
|
+
" runs-on: ubuntu-latest",
|
|
540
|
+
" timeout-minutes: 10",
|
|
541
|
+
" steps:",
|
|
542
|
+
" - name: Check out the pull request head",
|
|
543
|
+
` uses: ${CHECKOUT}`,
|
|
544
|
+
" with:",
|
|
545
|
+
" ref: ${{ github.event.pull_request.head.sha }}",
|
|
546
|
+
" fetch-depth: 0",
|
|
547
|
+
" persist-credentials: false",
|
|
548
|
+
"",
|
|
549
|
+
" # The branch name and both commits reach the script only through the",
|
|
550
|
+
" # environment, never through its text.",
|
|
551
|
+
" - name: Check the changed paths",
|
|
552
|
+
" env:",
|
|
553
|
+
" HEAD_REF: ${{ github.event.pull_request.head.ref }}",
|
|
554
|
+
" BASE_SHA: ${{ github.event.pull_request.base.sha }}",
|
|
555
|
+
" HEAD_SHA: ${{ github.event.pull_request.head.sha }}",
|
|
556
|
+
" run: |",
|
|
557
|
+
` node --input-type=module - <<'${HEREDOC_TERMINATOR}'`,
|
|
558
|
+
];
|
|
559
|
+
const RUN_BODY_INDENT = " ";
|
|
560
|
+
/** The bytes of `.github/workflows/clossys-path-scope.yml`: the script embedded in a quoted heredoc. */
|
|
561
|
+
export function renderPathScopeWorkflow(agentProvenance = undefined) {
|
|
562
|
+
const body = renderPathScopeScript(agentProvenance).slice(0, -1)
|
|
563
|
+
.split("\n")
|
|
564
|
+
.map((line) => (line === "" ? "" : `${RUN_BODY_INDENT}${line}`));
|
|
565
|
+
const head = agentProvenance === undefined ? PATH_SCOPE_WORKFLOW_HEAD : PATH_SCOPE_WORKFLOW_HEAD.map((line) => line.replace("head branch starts with clossys/apply-.", "head branch is a legacy or supported agent apply branch."));
|
|
566
|
+
return [...head, ...body, `${RUN_BODY_INDENT}${HEREDOC_TERMINATOR}`, ""].join("\n");
|
|
567
|
+
}
|
|
568
|
+
//# sourceMappingURL=setup-template-scripts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-template-scripts.js","sourceRoot":"","sources":["../src/setup-template-scripts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type TemplateAct } from "./change-set-contract.js";
|
|
2
|
+
export type SetupPackageManager = "npm" | "pnpm";
|
|
3
|
+
export interface StarterPinInput {
|
|
4
|
+
readonly name: string;
|
|
5
|
+
readonly version: string;
|
|
6
|
+
readonly integrity: string;
|
|
7
|
+
}
|
|
8
|
+
export interface StarterRequestInput {
|
|
9
|
+
readonly packageManager: unknown;
|
|
10
|
+
readonly repository: string;
|
|
11
|
+
readonly starter: StarterPinInput;
|
|
12
|
+
}
|
|
13
|
+
export interface TemplateRefusal {
|
|
14
|
+
readonly reason: "starter-pin-unsupported" | "package-manager-unsupported" | "input-invalid";
|
|
15
|
+
/** A position such as `starter.version`. Never the offending value. */
|
|
16
|
+
readonly at: string;
|
|
17
|
+
}
|
|
18
|
+
export interface TemplateFile {
|
|
19
|
+
readonly path: string;
|
|
20
|
+
readonly bytes: string;
|
|
21
|
+
}
|
|
22
|
+
export type TemplateResult = {
|
|
23
|
+
readonly ok: true;
|
|
24
|
+
readonly files: readonly TemplateFile[];
|
|
25
|
+
} | {
|
|
26
|
+
readonly ok: false;
|
|
27
|
+
readonly refusal: TemplateRefusal;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* The Starter versions a setup may pin, for documentation. The check below is
|
|
31
|
+
* the rule: an exact `0.2.PATCH` or `0.3.PATCH`, with no range, prerelease, build metadata, or
|
|
32
|
+
* leading zero.
|
|
33
|
+
*/
|
|
34
|
+
export declare const STARTER_PIN_RANGE = ">=0.2.0 <0.4.0";
|
|
35
|
+
/**
|
|
36
|
+
* Whether `version` is a Starter version the templates support: exactly the
|
|
37
|
+
* rule renderStarterRequest() applies, so a repository is read as past its
|
|
38
|
+
* setup only at a pin these templates would themselves have written.
|
|
39
|
+
*/
|
|
40
|
+
export declare function isSupportedStarterVersion(version: unknown): boolean;
|
|
41
|
+
/** Render `.starter/request.json`, or refuse at the first position, in the fixed order, that is not acceptable. */
|
|
42
|
+
export declare function renderStarterRequest(input: unknown): TemplateResult;
|
|
43
|
+
/** Render `.github/workflows/clossys-adoption-decision.yml` for npm or pnpm, or refuse at `packageManager`. */
|
|
44
|
+
export declare function renderAdoptionDecisionWorkflow(packageManager: unknown): TemplateResult;
|
|
45
|
+
/** Render `.github/workflows/clossys-ci.yml`. */
|
|
46
|
+
export declare function renderProductCiWorkflow(): string;
|
|
47
|
+
/**
|
|
48
|
+
* Render the files one setup act writes, or refuse. `add-caller-workflow` takes
|
|
49
|
+
* `{ packageManager }`, `write-starter-request` takes a `StarterRequestInput`,
|
|
50
|
+
* `add-ci-template` takes no input, and `add-path-scope-job` optionally takes
|
|
51
|
+
* `{ agentProvenance }`. The returned
|
|
52
|
+
* paths are always `TEMPLATE_PATHS[act]`, in that order.
|
|
53
|
+
*/
|
|
54
|
+
export declare function renderSetupTemplate(act: TemplateAct, input?: unknown): TemplateResult;
|
|
55
|
+
//# sourceMappingURL=setup-templates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-templates.d.ts","sourceRoot":"","sources":["../src/setup-templates.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAG5E,MAAM,MAAM,mBAAmB,GAAG,KAAK,GAAG,MAAM,CAAC;AAEjD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;CACnC;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,yBAAyB,GAAG,6BAA6B,GAAG,eAAe,CAAC;IAC7F,uEAAuE;IACvE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,cAAc,GACtB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAA;CAAE,GAC9D;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAA;CAAE,CAAC;AAE9D;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,mBAAmB,CAAC;AAWlD;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAEnE;AAwDD,mHAAmH;AACnH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CA6CnE;AA2ID,+GAA+G;AAC/G,wBAAgB,8BAA8B,CAAC,cAAc,EAAE,OAAO,GAAG,cAAc,CAGtF;AA6FD,iDAAiD;AACjD,wBAAgB,uBAAuB,IAAI,MAAM,CAEhD;AAwCD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,cAAc,CAkBrF"}
|