@echopath-labs/forgerail 0.1.0-alpha.2 → 0.1.0-alpha.4
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/.codex-plugin/plugin.json +2 -3
- package/CHANGELOG.md +22 -1
- package/CODE_OF_CONDUCT.md +34 -0
- package/CONTRIBUTING.md +68 -4
- package/README.md +126 -49
- package/README.zh-CN.md +131 -28
- package/SECURITY.md +48 -4
- package/SUPPORT.md +37 -0
- package/adapters/claude-code.json +6 -1
- package/adapters/codex.json +6 -0
- package/adapters/cursor.json +5 -0
- package/contracts/adoption-plan.schema.json +39 -18
- package/contracts/effective-profile.schema.json +4 -4
- package/contracts/host-adapter.schema.json +66 -4
- package/contracts/host-binding-receipt.schema.json +1 -1
- package/contracts/launch-contract.schema.json +38 -2
- package/contracts/profile-change-candidate.schema.json +1 -1
- package/contracts/return-receipt.schema.json +1 -1
- package/contracts/task-envelope.schema.json +1 -1
- package/directory/README.md +1 -1
- package/directory/release-notes-alpha3.md +7 -0
- package/directory/release-notes-alpha4.md +9 -0
- package/directory/submission-candidate.json +5 -6
- package/docs/adoption.md +63 -26
- package/docs/adoption.zh-CN.md +62 -25
- package/docs/architecture-acceptance.md +1 -1
- package/docs/composable-autonomy.zh-CN.md +16 -22
- package/docs/installation.md +72 -40
- package/docs/installation.zh-CN.md +90 -31
- package/docs/release-alpha3.md +25 -0
- package/docs/release-alpha3.zh-CN.md +25 -0
- package/docs/release-alpha4.md +33 -0
- package/docs/release-alpha4.zh-CN.md +33 -0
- package/package.json +7 -3
- package/scripts/adoption-closeout-regressions.mjs +100 -0
- package/scripts/build-universal-directory-candidate.mjs +2 -2
- package/scripts/disposable-consumer.mjs +11 -18
- package/scripts/fixtures/contracts/adoption-plan.multi-host.valid.json +16 -7
- package/scripts/fixtures/contracts/adoption-plan.mutating.invalid.json +6 -3
- package/scripts/fixtures/contracts/adoption-plan.single-host.valid.json +9 -4
- package/scripts/fixtures/contracts/effective-profile.duplicate-rule.invalid.json +1 -1
- package/scripts/fixtures/contracts/effective-profile.valid.json +3 -4
- package/scripts/fixtures/contracts/host-adapter.claude-code.profile-only.valid.json +6 -1
- package/scripts/fixtures/contracts/host-adapter.codex.valid.json +6 -0
- package/scripts/fixtures/contracts/host-adapter.cursor.profile-only.valid.json +5 -0
- package/scripts/fixtures/contracts/host-adapter.false-supported.invalid.json +6 -1
- package/scripts/fixtures/contracts/launch-contract.execution-owner.invalid.json +5 -1
- package/scripts/fixtures/contracts/launch-contract.valid.json +5 -1
- package/scripts/fixtures/open-source-docs/cases.json +65 -0
- package/scripts/forgerail.mjs +61 -16
- package/scripts/integrity-regressions.mjs +1261 -0
- package/scripts/lib/adoption.mjs +666 -51
- package/scripts/lib/bounded-read.mjs +80 -0
- package/scripts/lib/composition.mjs +77 -7
- package/scripts/lib/contracts.mjs +126 -40
- package/scripts/lib/diagnosis.mjs +146 -39
- package/scripts/shadow-comparison.mjs +52 -34
- package/scripts/validate-open-source-docs.mjs +132 -0
- package/scripts/validate-release.mjs +77 -13
- package/scripts/validate-universal-directory.mjs +17 -5
- package/skills/forgerail/references/adoption.md +2 -2
- package/skills/forgerail/references/contracts.md +2 -2
- package/scripts/lib/bundle.mjs +0 -77
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
4
6
|
import { dirname, resolve } from "node:path";
|
|
5
7
|
import { fileURLToPath } from "node:url";
|
|
6
8
|
|
|
7
9
|
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
10
|
const expectedPackageName = "@echopath-labs/forgerail";
|
|
9
|
-
const expectedVersion = "0.1.0-alpha.
|
|
11
|
+
const expectedVersion = "0.1.0-alpha.4";
|
|
10
12
|
const expectedTag = `v${expectedVersion}`;
|
|
11
|
-
const expectedDate = "2026-
|
|
13
|
+
const expectedDate = "2026-09-01";
|
|
12
14
|
const expectedPlugins = [
|
|
13
15
|
"forgerail",
|
|
14
16
|
"forgerail-cross-workspace-orchestration",
|
|
@@ -38,6 +40,30 @@ function findExisting(candidates) {
|
|
|
38
40
|
|
|
39
41
|
const packageJson = json("package.json");
|
|
40
42
|
const packageLock = json("package-lock.json");
|
|
43
|
+
const launchContractSchema = json("contracts/launch-contract.schema.json");
|
|
44
|
+
const effectiveProfileSchema = json("contracts/effective-profile.schema.json");
|
|
45
|
+
const publicCli = read("scripts/forgerail.mjs");
|
|
46
|
+
const packCache = mkdtempSync(resolve(tmpdir(), "forgerail-pack-cache-"));
|
|
47
|
+
const packEnvironment = Object.fromEntries(
|
|
48
|
+
Object.entries(process.env).filter(([key]) => key.toLowerCase() !== "npm_config_cache"),
|
|
49
|
+
);
|
|
50
|
+
packEnvironment.NPM_CONFIG_CACHE = packCache;
|
|
51
|
+
let packResult;
|
|
52
|
+
try {
|
|
53
|
+
packResult = spawnSync("npm", ["pack", "--dry-run", "--json", "--ignore-scripts"], {
|
|
54
|
+
cwd: root,
|
|
55
|
+
encoding: "utf8",
|
|
56
|
+
env: packEnvironment,
|
|
57
|
+
});
|
|
58
|
+
} finally {
|
|
59
|
+
rmSync(packCache, { recursive: true, force: true });
|
|
60
|
+
}
|
|
61
|
+
let packedFiles = [];
|
|
62
|
+
try {
|
|
63
|
+
packedFiles = packResult.status === 0
|
|
64
|
+
? JSON.parse(packResult.stdout)[0]?.files?.map(({ path }) => path) ?? []
|
|
65
|
+
: [];
|
|
66
|
+
} catch {}
|
|
41
67
|
record("package-name", packageJson.name === expectedPackageName, packageJson.name);
|
|
42
68
|
record("package-lock-name", packageLock.name === expectedPackageName && packageLock.packages?.[""]?.name === expectedPackageName, packageLock.name);
|
|
43
69
|
record("package-version", packageJson.version === expectedVersion, packageJson.version);
|
|
@@ -45,9 +71,26 @@ record("package-lock-version", packageLock.version === expectedVersion && packag
|
|
|
45
71
|
record("package-license", packageJson.license === "Apache-2.0", packageJson.license);
|
|
46
72
|
record("package-lock-license", packageLock.packages?.[""]?.license === "Apache-2.0", packageLock.packages?.[""]?.license ?? null);
|
|
47
73
|
record("npm-next-tag", packageJson.publishConfig?.tag === "next", packageJson.publishConfig?.tag ?? null);
|
|
74
|
+
record("no-public-bundle-builder-command", !publicCli.includes('command === "build-bundle"'), "source-repository maintainer tool only");
|
|
75
|
+
record("npm-pack-dry-run", packResult.status === 0 && packedFiles.length > 0, packResult.status === 0 ? `${packedFiles.length} files` : packResult.stderr.trim());
|
|
76
|
+
record("bundle-builder-source-only", existsSync(resolve(root, "tools/lib/bundle.mjs")) && !packedFiles.includes("tools/lib/bundle.mjs"), "tools/lib/bundle.mjs");
|
|
77
|
+
record(
|
|
78
|
+
"launch-requested-pack-schema-native-binding",
|
|
79
|
+
launchContractSchema.properties?.envelope?.properties?.packs?.type === "object"
|
|
80
|
+
&& launchContractSchema.properties?.envelope?.properties?.packs?.additionalProperties?.pattern === "^[0-9a-f]{64}$",
|
|
81
|
+
launchContractSchema.properties?.envelope?.properties?.packs ?? null,
|
|
82
|
+
);
|
|
83
|
+
record(
|
|
84
|
+
"profile-pack-schema-native-identity",
|
|
85
|
+
effectiveProfileSchema.properties?.packs?.type === "object"
|
|
86
|
+
&& effectiveProfileSchema.properties?.packs?.propertyNames?.pattern === "^[a-z][a-z0-9-]+$"
|
|
87
|
+
&& effectiveProfileSchema.properties?.packs?.additionalProperties?.required?.includes("state")
|
|
88
|
+
&& effectiveProfileSchema.properties?.packs?.additionalProperties?.required?.includes("reason"),
|
|
89
|
+
effectiveProfileSchema.properties?.packs ?? null,
|
|
90
|
+
);
|
|
48
91
|
record(
|
|
49
92
|
"prepublish-gate",
|
|
50
|
-
["npm test", "npm run test:shadow", "npm run test:release", "npm run test:consumer", "npm run test:directory"].every((command) => packageJson.scripts?.prepublishOnly?.includes(command)),
|
|
93
|
+
["npm test", "npm run test:integrity", "npm run test:shadow", "npm run test:release", "npm run test:consumer", "npm run test:directory"].every((command) => packageJson.scripts?.prepublishOnly?.includes(command)),
|
|
51
94
|
packageJson.scripts?.prepublishOnly ?? null,
|
|
52
95
|
);
|
|
53
96
|
|
|
@@ -89,7 +132,11 @@ for (const phrase of ["Workspace Diagnosis", "Return Receipts", "GitHub Rulesets
|
|
|
89
132
|
record(`changelog-${phrase.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-")}`, changelog.includes(phrase), phrase);
|
|
90
133
|
}
|
|
91
134
|
|
|
92
|
-
const marketplacePath = findExisting([
|
|
135
|
+
const marketplacePath = findExisting([
|
|
136
|
+
"marketplace/.agents/plugins/marketplace.json",
|
|
137
|
+
".agents/plugins/marketplace.json",
|
|
138
|
+
"../../.agents/plugins/marketplace.json",
|
|
139
|
+
]);
|
|
93
140
|
const marketplace = json(marketplacePath);
|
|
94
141
|
const marketplacePlugins = new Map(marketplace.plugins.map((plugin) => [plugin.name, plugin]));
|
|
95
142
|
record("marketplace-name", marketplace.name === "echopath-labs", marketplace.name);
|
|
@@ -99,15 +146,17 @@ for (const name of expectedPlugins) {
|
|
|
99
146
|
if (name !== "forgerail") record(`marketplace-${name}-on-use`, plugin?.policy?.authentication === "ON_USE", plugin?.policy?.authentication ?? null);
|
|
100
147
|
}
|
|
101
148
|
|
|
102
|
-
const installation = `${read("docs/installation.md")}\n${read("docs/installation.zh-CN.md")}`;
|
|
149
|
+
const installation = `${read("docs/installation.md")}\n${read("docs/installation.zh-CN.md")}\n${read("docs/adoption.md")}\n${read("docs/adoption.zh-CN.md")}`;
|
|
103
150
|
for (const phrase of [
|
|
104
|
-
|
|
151
|
+
`codex plugin marketplace add echopath-labs/forgerail --ref ${expectedTag}`,
|
|
105
152
|
"codex plugin add forgerail@echopath-labs",
|
|
106
153
|
"codex plugin add forgerail-cross-workspace-orchestration@echopath-labs",
|
|
107
154
|
"codex plugin add forgerail-github-rulesets@echopath-labs",
|
|
108
155
|
`${expectedPackageName}@${expectedVersion}`,
|
|
109
156
|
"new Codex task",
|
|
110
157
|
"adoption-plan --workspace . --host codex",
|
|
158
|
+
"adoption-plan --workspace . --selection all-detected",
|
|
159
|
+
"adoption-plan --workspace . --selection all-available",
|
|
111
160
|
"Host Binding Receipt",
|
|
112
161
|
]) {
|
|
113
162
|
record(`installation-${phrase.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-")}`, installation.includes(phrase), phrase);
|
|
@@ -122,6 +171,9 @@ for (const path of [
|
|
|
122
171
|
"adapters/cursor.json",
|
|
123
172
|
"templates/FORGERAIL.md",
|
|
124
173
|
"templates/bindings/codex-compact.md",
|
|
174
|
+
"templates/bindings/codex-thin.md",
|
|
175
|
+
"templates/bindings/claude-code-thin.md",
|
|
176
|
+
"templates/bindings/cursor-thin.mdc",
|
|
125
177
|
"docs/adoption.md",
|
|
126
178
|
"docs/adoption.zh-CN.md",
|
|
127
179
|
]) record(`adoption-path-${path.replaceAll(/[^a-z0-9]+/gi, "-").toLowerCase()}`, existsSync(resolve(root, path)), path);
|
|
@@ -129,15 +181,24 @@ for (const path of [
|
|
|
129
181
|
const codexAdapter = json("adapters/codex.json");
|
|
130
182
|
const claudeAdapter = json("adapters/claude-code.json");
|
|
131
183
|
const cursorAdapter = json("adapters/cursor.json");
|
|
132
|
-
record("codex-adapter-supported", codexAdapter.status === "supported" && codexAdapter.bindingTarget === "AGENTS.md", codexAdapter.status);
|
|
133
|
-
record("claude-adapter-profile-only", claudeAdapter.status === "profile-only", claudeAdapter.status);
|
|
134
|
-
record("
|
|
184
|
+
record("codex-adapter-supported", codexAdapter.status === "supported" && codexAdapter.bindingTarget === "AGENTS.md" && codexAdapter.detectionTargets?.includes("AGENTS.md"), codexAdapter.status);
|
|
185
|
+
record("claude-adapter-profile-only", claudeAdapter.status === "profile-only" && claudeAdapter.detectionTargets?.includes("CLAUDE.md"), claudeAdapter.status);
|
|
186
|
+
record("claude-adapter-thin-only", JSON.stringify(claudeAdapter.bindingModes) === JSON.stringify(["thin-reference"]), claudeAdapter.bindingModes);
|
|
187
|
+
record("cursor-adapter-profile-only", cursorAdapter.status === "profile-only" && cursorAdapter.detectionTargets?.includes(".cursor"), cursorAdapter.status);
|
|
188
|
+
for (const adapter of [codexAdapter, claudeAdapter, cursorAdapter]) {
|
|
189
|
+
const modes = Object.keys(adapter.bindingTemplates ?? {}).sort();
|
|
190
|
+
record(`adapter-${adapter.id}-template-modes`, JSON.stringify(modes) === JSON.stringify([...adapter.bindingModes].sort()), { modes, bindingModes: adapter.bindingModes });
|
|
191
|
+
record(`adapter-${adapter.id}-all-host-thin-reference`, adapter.bindingModes.includes("thin-reference"), adapter.bindingModes);
|
|
192
|
+
for (const [mode, template] of Object.entries(adapter.bindingTemplates ?? {})) {
|
|
193
|
+
record(`adapter-${adapter.id}-${mode}-template`, existsSync(resolve(root, "templates", template)), template);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
135
196
|
record("package-adapters", packageJson.files?.includes("adapters/"), packageJson.files ?? null);
|
|
136
197
|
record("package-templates", packageJson.files?.includes("templates/"), packageJson.files ?? null);
|
|
137
198
|
record("no-apply-adoption-script", !read("scripts/forgerail.mjs").includes('command === "apply-adoption"'), "no apply-adoption command");
|
|
138
199
|
|
|
139
|
-
const releaseEnglish = read("docs/release-
|
|
140
|
-
const releaseChinese = read("docs/release-
|
|
200
|
+
const releaseEnglish = read("docs/release-alpha4.md");
|
|
201
|
+
const releaseChinese = read("docs/release-alpha4.zh-CN.md");
|
|
141
202
|
const releaseDocs = `${releaseEnglish}\n${releaseChinese}`;
|
|
142
203
|
for (const phrase of [
|
|
143
204
|
"remote_integration_approval",
|
|
@@ -146,7 +207,7 @@ for (const phrase of [
|
|
|
146
207
|
expectedVersion,
|
|
147
208
|
expectedTag,
|
|
148
209
|
"Node.js 22 and 24",
|
|
149
|
-
"codex/forgerail-
|
|
210
|
+
"codex/forgerail-alpha4-critical-integrity",
|
|
150
211
|
"Do not unpublish",
|
|
151
212
|
"AGW",
|
|
152
213
|
"Host Binding Receipt",
|
|
@@ -179,8 +240,11 @@ record("runbook-no-fixed-external-pack-count", !releaseChinese.includes("三个
|
|
|
179
240
|
const workflow = read(".github/workflows/plugin-contracts.yml");
|
|
180
241
|
record("ci-node-22", workflow.includes("- 22"), "Node.js 22");
|
|
181
242
|
record("ci-node-24", workflow.includes("- 24"), "Node.js 24");
|
|
243
|
+
record("ci-full-core", workflow.includes("run: npm test"), "npm test");
|
|
244
|
+
record("ci-integrity-regressions", workflow.includes("run: npm run test:integrity"), "npm run test:integrity");
|
|
182
245
|
record("ci-release-source", workflow.includes("node scripts/validate-release.mjs"), "release source validator");
|
|
183
246
|
record("ci-progressive-adoption", workflow.includes("node scripts/forgerail.mjs validate-adoption"), "progressive adoption validator");
|
|
247
|
+
record("ci-directory", workflow.includes("node scripts/validate-universal-directory.mjs"), "Universal Directory validator");
|
|
184
248
|
|
|
185
249
|
const failures = checks.filter((check) => !check.passed);
|
|
186
250
|
const report = {
|
|
@@ -64,11 +64,11 @@ function validateMarkdownLinks(relativePath) {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
assert(candidate.submissionType === "skills_only", "candidate is Skills-only");
|
|
67
|
-
assert(candidate.status === "
|
|
67
|
+
assert(candidate.status === "local_alpha4_integrity_candidate", "candidate status is local alpha.4 integrity preparation");
|
|
68
68
|
assert(candidate.approval.status === "not_granted", "submission approval is not granted");
|
|
69
69
|
assert(candidate.plugin.id === manifest.name, "candidate and manifest Plugin identity match");
|
|
70
70
|
assert(candidate.plugin.version === manifest.version, "candidate and manifest version match");
|
|
71
|
-
assert(candidate.plugin.version === "0.1.0-alpha.
|
|
71
|
+
assert(candidate.plugin.version === "0.1.0-alpha.4", "candidate version is alpha.4");
|
|
72
72
|
assert(packageJson.version === candidate.plugin.version, "optional scoped package version matches candidate");
|
|
73
73
|
assert(candidate.plugin.mcpServers.length === 0, "candidate has no MCP server requirement");
|
|
74
74
|
assert(candidate.plugin.authentication === "none", "candidate has no authentication requirement");
|
|
@@ -76,6 +76,18 @@ assert(candidate.plugin.coreRequirements.projectPackageJson === false, "Core req
|
|
|
76
76
|
assert(candidate.plugin.coreRequirements.projectNodeModules === false, "Core requires no project node_modules");
|
|
77
77
|
assert(candidate.plugin.coreRequirements.npmCli === "optional", "npm CLI is optional");
|
|
78
78
|
|
|
79
|
+
const expectedDefaultPrompts = [
|
|
80
|
+
"Use ForgeRail to govern this engineering task.",
|
|
81
|
+
"Diagnose this workspace before recommending governance changes.",
|
|
82
|
+
"Review workspace health or audit duplicated architecture ownership using the matching independent ForgeRail Skill.",
|
|
83
|
+
];
|
|
84
|
+
assert(Array.isArray(manifest.interface.defaultPrompt), "defaultPrompt is an array");
|
|
85
|
+
assert(manifest.interface.defaultPrompt.length <= 3, "defaultPrompt stays within the Codex maximum of three");
|
|
86
|
+
assert(manifest.interface.defaultPrompt.length === expectedDefaultPrompts.length, "defaultPrompt exposes the three reviewed intents");
|
|
87
|
+
assert(new Set(manifest.interface.defaultPrompt).size === manifest.interface.defaultPrompt.length, "defaultPrompt entries are unique");
|
|
88
|
+
assert(manifest.interface.defaultPrompt.every((prompt) => typeof prompt === "string" && prompt.trim().length > 0), "defaultPrompt entries are non-empty strings");
|
|
89
|
+
assert(JSON.stringify(manifest.interface.defaultPrompt) === JSON.stringify(expectedDefaultPrompts), "defaultPrompt entries match the reviewed governance, diagnosis, and either-or review router");
|
|
90
|
+
|
|
79
91
|
for (const skill of candidate.plugin.skills) {
|
|
80
92
|
assert(existsSync(resolve(pluginRoot, "skills", skill, "SKILL.md")), `declared Skill exists: ${skill}`);
|
|
81
93
|
}
|
|
@@ -96,8 +108,8 @@ for (const field of ["websiteUrl", "supportUrl", "privacyPolicyUrl", "termsOfSer
|
|
|
96
108
|
}
|
|
97
109
|
assert(candidate.availability.intent.state === "confirmed_by_user" && candidate.availability.intent.value === "all_platform_supported_regions", "all-platform-supported-regions intent is user-confirmed");
|
|
98
110
|
assert(candidate.availability.portalEnumeration.state === "pending_confirmation" && candidate.availability.portalEnumeration.values.length === 0, "portal region enumeration remains pending without an invented country list");
|
|
99
|
-
assert(candidate.releaseNotes.state === "candidate" && candidate.releaseNotes.path === "./directory/release-notes-
|
|
100
|
-
assert(existsSync(resolve(pluginRoot, candidate.releaseNotes.path)), "alpha.
|
|
111
|
+
assert(candidate.releaseNotes.state === "candidate" && candidate.releaseNotes.path === "./directory/release-notes-alpha4.md", "alpha.4 release notes path is explicit");
|
|
112
|
+
assert(existsSync(resolve(pluginRoot, candidate.releaseNotes.path)), "alpha.4 release notes file exists");
|
|
101
113
|
|
|
102
114
|
const privacy = readFileSync(resolve(pluginRoot, "PRIVACY.md"), "utf8");
|
|
103
115
|
const terms = readFileSync(resolve(pluginRoot, "TERMS.md"), "utf8");
|
|
@@ -107,7 +119,7 @@ for (const phrase of ["Skills-only Agent Plugin", "does not operate its own serv
|
|
|
107
119
|
for (const phrase of ["Apache License 2.0", "without warranties", "You are responsible", "external actions", "does not provide legal advice", "service-level agreement"]) {
|
|
108
120
|
assert(terms.includes(phrase), `Terms contain required boundary: ${phrase}`);
|
|
109
121
|
}
|
|
110
|
-
for (const path of ["PRIVACY.md", "TERMS.md", "README.md", "README.zh-CN.md", "docs/installation.md", "docs/installation.zh-CN.md", "docs/release-
|
|
122
|
+
for (const path of ["PRIVACY.md", "TERMS.md", "README.md", "README.zh-CN.md", "docs/installation.md", "docs/installation.zh-CN.md", "docs/release-alpha4.md", "docs/release-alpha4.zh-CN.md"]) {
|
|
111
123
|
validateMarkdownLinks(path);
|
|
112
124
|
}
|
|
113
125
|
|
|
@@ -6,8 +6,8 @@ Use the minimum level:
|
|
|
6
6
|
2. `lightweight-adoption`: user-confirmed host instruction binding.
|
|
7
7
|
3. `persisted-governance`: evidence-gated and deferred in alpha.1.
|
|
8
8
|
|
|
9
|
-
For one host, propose one versioned managed block
|
|
9
|
+
For one host, propose one versioned managed block when its adapter supports that mode. For multiple hosts, or one thin-reference-only host, propose `FORGERAIL.md` plus thin references. Host files are adapters, not Core sources.
|
|
10
10
|
|
|
11
|
-
Run `forgerail adoption-plan --workspace <path>
|
|
11
|
+
Translate the user's natural-language host intent into one deterministic selection: repeated `--host <adapter>` for an explicit subset, `--selection all-detected` for registry adapters evidenced in the workspace, or `--selection all-available` for every adapter in the current registry. Omitting both options defaults to `all-detected`. Do not invent a host ID or instruction path; an unknown host needs a reviewed Host Adapter. Run `forgerail adoption-plan --workspace <path> ...` when the deterministic CLI is available. Never infer permission to apply the returned writes. Display the retained selection, exact content, paths and each write's `approvalSha256`, obtain confirmation, and preserve the approved digest separately from the mutable proposal. Node-based integrations must pass that retained digest as the third argument to `applyApprovedAdoptionWrite()` from `scripts/lib/adoption.mjs` to revalidate the canonical workspace identity and complete executable write metadata from one immutable snapshot, plus confinement, no-follow open, file identity and base digest at write time. Produce a Host Binding Receipt after verification.
|
|
12
12
|
|
|
13
13
|
Codex is `supported` in alpha.1. Claude Code and Cursor are `profile-only`; do not close their binding as verified without a host-specific activation check.
|
|
@@ -16,7 +16,7 @@ Task authorization expires with the task. Never promote it into workspace policy
|
|
|
16
16
|
|
|
17
17
|
## Launch Contract
|
|
18
18
|
|
|
19
|
-
Give the host Agent the Envelope plus the effective rule sources. Specify outcomes and boundaries, not unnecessary implementation steps.
|
|
19
|
+
Give the host Agent the resolved Envelope plus the effective rule sources. In the Launch Contract, `envelope.packs` is an identity-to-canonical-manifest-digest map rather than the Task Envelope's pre-resolution id list. This makes every requested Pack self-bound for schema-only consumers; `effectivePackManifests` retains the complete active Pack set. Specify outcomes and boundaries, not unnecessary implementation steps.
|
|
20
20
|
|
|
21
21
|
## Return Receipt
|
|
22
22
|
|
|
@@ -37,7 +37,7 @@ Compare the receipt with observable state. A mismatch keeps closeout incomplete.
|
|
|
37
37
|
Record:
|
|
38
38
|
|
|
39
39
|
- current and proposed adoption level;
|
|
40
|
-
-
|
|
40
|
+
- deterministically resolved Host Adapters, their selection mode, and their support status;
|
|
41
41
|
- exact target paths, operations, base SHA-256 digests, candidate content, and content digests;
|
|
42
42
|
- required user confirmation and activation verification;
|
|
43
43
|
- confirmed non-mutations.
|
package/scripts/lib/bundle.mjs
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
3
|
-
import { dirname, relative, resolve, sep } from "node:path";
|
|
4
|
-
|
|
5
|
-
const roots = [".codex-plugin", ".github", "adapters", "contracts", "docs", "packs", "scripts", "skills", "templates"];
|
|
6
|
-
const files = ["CHANGELOG.md", "CONTRIBUTING.md", "LICENSE", "NOTICE", "PLUGIN.md", "README.md", "README.zh-CN.md", "SECURITY.md", "package.json"];
|
|
7
|
-
const catalog = "marketplace/.agents/plugins/marketplace.json";
|
|
8
|
-
const externalPluginNames = [
|
|
9
|
-
"forgerail-cross-workspace-orchestration",
|
|
10
|
-
"forgerail-github-rulesets",
|
|
11
|
-
"forgerail-release-safety",
|
|
12
|
-
"forgerail-thread-closure",
|
|
13
|
-
];
|
|
14
|
-
|
|
15
|
-
function below(base, prefix, result = []) {
|
|
16
|
-
for (const entry of readdirSync(resolve(base, prefix), { withFileTypes: true })) {
|
|
17
|
-
const path = `${prefix}/${entry.name}`;
|
|
18
|
-
if (entry.isDirectory()) below(base, path, result);
|
|
19
|
-
else if (entry.isFile()) result.push(path);
|
|
20
|
-
else throw new Error(`unsupported entry: ${path}`);
|
|
21
|
-
}
|
|
22
|
-
return result;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function buildBundle(root, output) {
|
|
26
|
-
const target = resolve(output);
|
|
27
|
-
const relativeToTmp = relative("/private/tmp", target);
|
|
28
|
-
const relativeToSystemTmp = relative("/tmp", target);
|
|
29
|
-
const safe = (value) => value !== "" && value !== ".." && !value.startsWith(`..${sep}`) && !value.startsWith("/");
|
|
30
|
-
if (!safe(relativeToTmp) && !safe(relativeToSystemTmp)) throw new Error("output must be a new directory below /private/tmp or /tmp");
|
|
31
|
-
if (existsSync(target)) throw new Error("output already exists");
|
|
32
|
-
for (const required of [...roots, ...files, catalog]) if (!existsSync(resolve(root, required))) throw new Error(`public bundle source is missing: ${required}`);
|
|
33
|
-
const payload = [...files, ...roots.flatMap((prefix) => below(root, prefix))].sort();
|
|
34
|
-
const externalPlugins = externalPluginNames.map((name) => {
|
|
35
|
-
const pluginRoot = resolve(root, `../${name}`);
|
|
36
|
-
if (!existsSync(pluginRoot)) throw new Error(`external Plugin source is missing: ${name}`);
|
|
37
|
-
return {
|
|
38
|
-
name,
|
|
39
|
-
root: pluginRoot,
|
|
40
|
-
files: below(pluginRoot, ".").map((path) => path.startsWith("./") ? path.slice(2) : path).sort(),
|
|
41
|
-
};
|
|
42
|
-
});
|
|
43
|
-
const inventory = [];
|
|
44
|
-
const projections = [
|
|
45
|
-
{ source: catalog, target: ".agents/plugins/marketplace.json" },
|
|
46
|
-
...payload.flatMap((path) => [
|
|
47
|
-
{ source: path, target: path },
|
|
48
|
-
{ source: path, target: `plugins/forgerail/${path}` },
|
|
49
|
-
]),
|
|
50
|
-
...externalPlugins.flatMap((plugin) => plugin.files.map((path) => ({
|
|
51
|
-
source: resolve(plugin.root, path),
|
|
52
|
-
target: `plugins/${plugin.name}/${path}`,
|
|
53
|
-
externalSource: `../${plugin.name}/${path}`,
|
|
54
|
-
absolute: true,
|
|
55
|
-
}))),
|
|
56
|
-
].sort((left, right) => left.target.localeCompare(right.target));
|
|
57
|
-
for (const { source: path, target: publicPath, externalSource, absolute = false } of projections) {
|
|
58
|
-
const source = absolute ? path : resolve(root, path);
|
|
59
|
-
if (!statSync(source).isFile()) throw new Error(`bundle source is not a file: ${path}`);
|
|
60
|
-
const destination = resolve(target, publicPath);
|
|
61
|
-
mkdirSync(dirname(destination), { recursive: true });
|
|
62
|
-
copyFileSync(source, destination);
|
|
63
|
-
const bytes = readFileSync(source);
|
|
64
|
-
inventory.push({ path: publicPath, source: absolute ? externalSource : path, bytes: bytes.length, sha256: createHash("sha256").update(bytes).digest("hex") });
|
|
65
|
-
}
|
|
66
|
-
const digest = createHash("sha256").update(`${JSON.stringify(inventory)}\n`).digest("hex");
|
|
67
|
-
return {
|
|
68
|
-
schemaVersion: "1.0",
|
|
69
|
-
productId: "forgerail",
|
|
70
|
-
projection: "marketplace-root-plus-nested-plugin",
|
|
71
|
-
fileCount: inventory.length,
|
|
72
|
-
totalBytes: inventory.reduce((sum, item) => sum + item.bytes, 0),
|
|
73
|
-
digest,
|
|
74
|
-
receiptDigest: createHash("sha256").update(`forgerail\n${digest}\n${inventory.length}\n`).digest("hex"),
|
|
75
|
-
files: inventory,
|
|
76
|
-
};
|
|
77
|
-
}
|