@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
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# ForgeRail 0.1.0-alpha.4 Integrity Runbook
|
|
2
|
+
|
|
3
|
+
This source-first runbook governs the proposed `@echopath-labs/forgerail@0.1.0-alpha.4`, tag `v0.1.0-alpha.4`, and matching Agent Plugin projection. It does not grant `remote_integration_approval`, `release_approval`, `submission_approval`, `rollback_approval`, or `lifecycle_change_approval`. The unscoped `forgerail@0.0.0-reserved.0` remains a reservation and must never receive product code or serve as an install or rollback source.
|
|
4
|
+
|
|
5
|
+
## Candidate integration
|
|
6
|
+
|
|
7
|
+
The public candidate is an ordinary child of the observed remote `main`. The observed base is exactly `e8fa29cd9f0f782d423d2fdd8abd778fbd362d61`. A future exact `remote_integration_approval` may push only the signed commit to `codex/forgerail-alpha4-critical-integrity`, create one Draft PR, and observe Node.js 22 and 24 Plugin Contracts CI. The Draft PR base and publication comparison baseline remain bound to the observed remote `main`.
|
|
8
|
+
|
|
9
|
+
Any correction is an ordinary source-first successor commit. Do not force push, rewrite the candidate, transition the PR to Ready, merge, tag, publish, or create a release under remote-integration authority. Install and discover each external Capability Pack separately; availability never implies authentication, enablement, or mutation authority.
|
|
10
|
+
|
|
11
|
+
## Integrity and compatibility proof
|
|
12
|
+
|
|
13
|
+
Run Core, integrity regressions, Shadow comparison, release source, Directory, disposable consumer and external Pack validation. Alpha.3 Profiles, Packs, Task Envelopes and Return Receipts that remain valid under the explicit alpha.4 constraints must continue to pass. Launch Contracts and Adoption Plans are intentionally tightened and require regeneration before alpha.4 validation. Invalid identifiers, dates, paths, duplicate identities, missing required Packs, malformed receipts, escaped adoption targets, unsafe bundle sources and inconsistent orchestration events must fail closed.
|
|
14
|
+
|
|
15
|
+
Each proposed Adoption write carries an `approvalSha256` over its canonical workspace identity and complete executable metadata: path, operation, base digest, content digest, content and managed marker. An integration must preserve the human-approved digest separately from the mutable proposal and provide it when applying the write; ForgeRail validates one immutable field snapshot, rejects every operation except `create`, `append-managed-block`, or `replace-managed-block`, and therefore fails workspace replay, accessor drift, unsupported-operation replay, or ordinary metadata drift before destination mutation. Replacing an existing managed binding uses a same-directory atomic rename while retaining a recovery hard link, so the target path is never deliberately absent and a missing installed leaf can be restored without masking the original failure. Four-digit contract years, including `0000` through `0099`, are interpreted literally rather than through JavaScript's legacy 1900 offset.
|
|
16
|
+
|
|
17
|
+
An existing managed binding is replaceable only when its opening and closing boundary markers each occur exactly once and in the expected order. Incomplete, reversed, duplicate-opening, or duplicate-closing structures fail before a proposal is generated and remain untouched.
|
|
18
|
+
|
|
19
|
+
The intentional alpha.3 compatibility tightenings are explicit: contract identifiers now require at least two characters and a leading alphanumeric character; every Launch Contract requires the validated `effectiveProfile` digest plus canonical identities for its active Pack manifests, while workspace identity appears only once at `envelope.ownerWorkspace`; its resolved `envelope.packs` is an identity-to-manifest-digest map so every requested Pack is schema-native and self-bound; Effective Profile `packs` is an identity-keyed object rather than an array, so alpha.3 Profiles must be regenerated; every required active Pack must have an available manifest and be included by the task envelope; Adoption paths must use canonical portable segments and every write in one plan must bind the same workspace identity. Launch construction sorts copies rather than mutating Task Envelope inputs, and projection directory deny lists are case-insensitive. These are fail-closed corrections, not silent compatibility regressions.
|
|
20
|
+
|
|
21
|
+
The source-repository projection builder is a maintainer tool and must not enter the npm package or public CLI. Public projection requires an explicit array-valued `package.json.files` publication allowlist, must be repeatable, reject sensitive filename families case-insensitively, reserve a new output directory without replacing a concurrent target, exclude private process evidence and preserve the exact public-main documentation baseline.
|
|
22
|
+
|
|
23
|
+
## Separate release gate
|
|
24
|
+
|
|
25
|
+
Only a later exact `release_approval` can authorize Ready/merge, scoped npm publication, dist-tags, annotated tag, GitHub prerelease, and disposable consumer verification. The merged public `main` tree must equal the final signed projection tree before release validation begins.
|
|
26
|
+
|
|
27
|
+
Run Node.js 22 and 24 checks for Core, contracts, integrity, external Packs, frozen AGW behavior coverage, release source, Directory readiness, disposable consumer lifecycle, pack metadata, and audit. Verify GitHub API/SSH/npm identity as `chasechou007` without exposing credentials. Preserve Apache-2.0, the unscoped reservation, and the immutable alpha.1 through alpha.3 package/tag history.
|
|
28
|
+
|
|
29
|
+
Do not unpublish or move an immutable version or Git tag. A shipped defect requires an ordinary source-first successor commit. Installation and diagnosis must leave project files unchanged unless the user separately approves an exact managed binding; return a Host Binding Receipt and confirm no implicit `.forgerail/` state.
|
|
30
|
+
|
|
31
|
+
## Independent Directory and lifecycle gates
|
|
32
|
+
|
|
33
|
+
Universal Plugins Directory draft, submission, review publication, verified publisher identity, Apps Management Write, portal regions and assets remain under a separate `submission_approval` or publication gate. AGW deprecation, redirect, archive, or deletion requires `lifecycle_change_approval`. No approval is transitive.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# ForgeRail 0.1.0-alpha.4 完整性发布 Runbook
|
|
2
|
+
|
|
3
|
+
本 source-first runbook 约束拟议的 `@echopath-labs/forgerail@0.1.0-alpha.4`、tag `v0.1.0-alpha.4` 与对应 Agent Plugin 投影。文档本身不授予 `remote_integration_approval`、`release_approval`、`submission_approval`、`rollback_approval` 或 `lifecycle_change_approval`。未作用域 `forgerail@0.0.0-reserved.0` 继续只作占位,不得接收产品代码,也不是安装或回滚来源。
|
|
4
|
+
|
|
5
|
+
## 候选集成
|
|
6
|
+
|
|
7
|
+
公共候选是已观测远端 `main` 的普通子 commit。该基线精确为 `e8fa29cd9f0f782d423d2fdd8abd778fbd362d61`。未来的精确 `remote_integration_approval` 最多允许把已签名 commit 推送到 `codex/forgerail-alpha4-critical-integrity`、创建一个 Draft PR,并观察 Node.js 22 与 24 Plugin Contracts CI。Draft PR base 与 publication comparison baseline 继续绑定已观测远端 `main`。
|
|
8
|
+
|
|
9
|
+
任何修正都必须形成普通的 source-first successor commit。远端集成授权不允许 force push、改写候选、把 PR 转为 Ready、合并、打 tag、发布 npm 或创建 Release。每个外部 Capability Pack 分别安装与发现;可见不等于已认证、已启用或具有变更权限。
|
|
10
|
+
|
|
11
|
+
## 完整性与兼容性证明
|
|
12
|
+
|
|
13
|
+
运行 Core、完整性回归、Shadow comparison、release source、Directory、一次性消费者与外部 Pack 验证。仍满足 alpha.4 显式约束的 alpha.3 Profile、Pack、Task Envelope 与 Return Receipt 必须继续通过;Launch Contract 与 Adoption Plan 已有意收紧,必须重新生成后再交给 alpha.4 validator。非法标识符、日期、路径、重复身份、缺少必需 Pack、畸形 receipt、逃逸 adoption target、不安全 bundle source 与矛盾 orchestration event 必须 fail closed。
|
|
14
|
+
|
|
15
|
+
每一条 Adoption 候选写入都携带 `approvalSha256`,绑定 canonical workspace 身份与完整可执行元数据:路径、操作、基线摘要、内容摘要、内容与 managed marker。集成方必须把人类已批准的摘要与可变候选分开保存,并在应用写入时显式传入;ForgeRail 只校验并使用一次不可变字段快照,且只接受 `create`、`append-managed-block`、`replace-managed-block` 三种操作,因此跨工作区重放、accessor 漂移、非法操作重放或普通元数据漂移都会在目标修改之前失败。替换既有 managed binding 时使用同目录原子 rename,并以 hard link 保留恢复入口,因此不会主动制造目标路径缺失的时间窗;若已安装 leaf 消失,可恢复原 binding 且不会遮蔽原始错误。四位年份(包括 `0000` 至 `0099`)按字面解释,不再触发 JavaScript 旧式的 1900 年偏移。
|
|
16
|
+
|
|
17
|
+
只有起始和结束边界标记都恰好出现一次且顺序正确时,既有 managed binding 才能被替换。不完整、顺序颠倒、重复起始或重复结束的结构都会在生成候选前失败,并保持原文件不变。
|
|
18
|
+
|
|
19
|
+
相对 alpha.3 的兼容性收紧必须明确:contract 标识符至少包含两个字符且首字符必须为字母或数字;每个 Launch Contract 必须携带已验证的 `effectiveProfile` 摘要以及当前 active Pack manifest 的规范身份摘要,工作区身份仅在 `envelope.ownerWorkspace` 出现一次;解析后的 `envelope.packs` 使用 Pack 身份到 manifest 摘要的映射,使每个 requested Pack 在公开 Schema 中直接自绑定;Effective Profile 的 `packs` 改为以 Pack 身份为键的对象,因此 alpha.3 Profile 必须重新生成;每个 required active Pack 必须存在可用 manifest,并由 task envelope 显式包含;Adoption 路径必须采用规范 portable segments,且一份计划中的所有写入必须绑定同一 workspace identity。Launch 构造只排序输入副本,不修改 Task Envelope;投影目录 denylist 不区分大小写。这些是 fail-closed 修正,不是未说明的兼容性回归。
|
|
20
|
+
|
|
21
|
+
source-repository projection builder 仅供维护者使用,不得进入 npm 包或公共 CLI。公共投影必须要求 `package.json.files` 是显式数组、保持可重复、以不区分大小写的方式拒绝敏感文件名族、先独占保留新输出目录且不得替换并发出现的目标、排除私有过程证据,并保留公共 main 的文档基线。
|
|
22
|
+
|
|
23
|
+
## 独立发布门
|
|
24
|
+
|
|
25
|
+
只有后续精确 `release_approval` 才能授权 Ready/merge、作用域 npm 发布、dist-tags、annotated tag、GitHub prerelease 与一次性消费者复验。合并后的公共 `main` tree 必须等于最终签名 projection tree,之后才能开始发布验证。
|
|
26
|
+
|
|
27
|
+
Node.js 22 和 24 必须验证 Core、contracts、integrity、外部 Packs、冻结 AGW 行为覆盖、release source、Directory readiness、一次性消费者生命周期、pack metadata 与 audit。GitHub API/SSH/npm 身份必须为 `chasechou007`,且不得暴露凭据。保留 Apache-2.0、未作用域占位包以及 alpha.1 至 alpha.3 的不可变 package/tag 历史。
|
|
28
|
+
|
|
29
|
+
不得 unpublish 或移动不可变版本和 Git tag。已发布缺陷只能通过普通的 source-first successor commit 修复。安装和诊断默认不得修改项目文件;只有用户另行批准精确 managed binding 后才可写入,并须返回 Host Binding Receipt,确认未隐式创建 `.forgerail/` 状态。
|
|
30
|
+
|
|
31
|
+
## 独立 Directory 与生命周期门
|
|
32
|
+
|
|
33
|
+
Universal Plugins Directory draft、submission、review publication、verified publisher identity、Apps Management Write、portal regions 与 assets 仍属于独立 `submission_approval` 或 publication gate。AGW deprecation、redirect、archive 或 deletion 需要 `lifecycle_change_approval`。任何审批都不可传递。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@echopath-labs/forgerail",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.4",
|
|
4
4
|
"description": "Deterministic validation and diagnosis CLI for the ForgeRail Agent Plugin.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"skills/",
|
|
21
21
|
"templates/",
|
|
22
22
|
"CHANGELOG.md",
|
|
23
|
+
"CODE_OF_CONDUCT.md",
|
|
23
24
|
"CONTRIBUTING.md",
|
|
24
25
|
"LICENSE",
|
|
25
26
|
"NOTICE",
|
|
@@ -28,20 +29,23 @@
|
|
|
28
29
|
"README.md",
|
|
29
30
|
"README.zh-CN.md",
|
|
30
31
|
"SECURITY.md",
|
|
32
|
+
"SUPPORT.md",
|
|
31
33
|
"TERMS.md"
|
|
32
34
|
],
|
|
33
35
|
"engines": {
|
|
34
36
|
"node": ">=22"
|
|
35
37
|
},
|
|
36
38
|
"scripts": {
|
|
37
|
-
"test": "node scripts/forgerail.mjs validate && node scripts/forgerail.mjs validate-fixtures && node scripts/forgerail.mjs validate-adoption",
|
|
39
|
+
"test": "node scripts/forgerail.mjs validate && node scripts/forgerail.mjs validate-fixtures && node scripts/forgerail.mjs validate-adoption && npm run test:docs",
|
|
40
|
+
"test:integrity": "node scripts/integrity-regressions.mjs",
|
|
41
|
+
"test:docs": "node scripts/validate-open-source-docs.mjs",
|
|
38
42
|
"test:consumer": "node scripts/disposable-consumer.mjs",
|
|
39
43
|
"test:release": "node scripts/validate-release.mjs",
|
|
40
44
|
"test:shadow": "node scripts/shadow-comparison.mjs",
|
|
41
45
|
"test:directory": "node scripts/validate-universal-directory.mjs",
|
|
42
46
|
"build:directory-candidate": "node scripts/build-universal-directory-candidate.mjs",
|
|
43
47
|
"diagnose:fixture": "node scripts/forgerail.mjs diagnose --workspace scripts/fixtures/workspaces/markdown-existing",
|
|
44
|
-
"prepublishOnly": "npm test && npm run test:shadow && npm run test:release && npm run test:consumer && npm run test:directory"
|
|
48
|
+
"prepublishOnly": "npm test && npm run test:integrity && npm run test:shadow && npm run test:release && npm run test:consumer && npm run test:directory"
|
|
45
49
|
},
|
|
46
50
|
"repository": {
|
|
47
51
|
"type": "git",
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { cpSync, existsSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { dirname, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import test from "node:test";
|
|
7
|
+
import { adoptionWriteApprovalDigest, loadHostAdapters, planAdoption } from "./lib/adoption.mjs";
|
|
8
|
+
import { validateContract } from "./lib/contracts.mjs";
|
|
9
|
+
|
|
10
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
11
|
+
function temporary(t, prefix) {
|
|
12
|
+
const path = mkdtempSync(resolve(tmpdir(), prefix));
|
|
13
|
+
t.after(() => rmSync(path, { recursive: true, force: true }));
|
|
14
|
+
return path;
|
|
15
|
+
}
|
|
16
|
+
function registry(t) {
|
|
17
|
+
const path = temporary(t, "forgerail-closeout-registry-");
|
|
18
|
+
for (const name of ["adapters", "templates"]) cpSync(resolve(root, name), resolve(path, name), { recursive: true });
|
|
19
|
+
return path;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
test("registry reports every non-object JSON root without crashing", (t) => {
|
|
23
|
+
const plugin = registry(t);
|
|
24
|
+
for (const value of [null, [], false, 1, "invalid"]) {
|
|
25
|
+
writeFileSync(resolve(plugin, "adapters/malformed.json"), JSON.stringify(value));
|
|
26
|
+
const result = loadHostAdapters(plugin);
|
|
27
|
+
assert.equal(result.valid, false);
|
|
28
|
+
assert.ok(result.errors.some((error) => error.startsWith("malformed.json:")));
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("received plans reject reserved, case-folded and ancestor target collisions", (t) => {
|
|
33
|
+
const workspace = temporary(t, "forgerail-closeout-plan-");
|
|
34
|
+
const plan = planAdoption(root, workspace, ["codex", "claude-code"]);
|
|
35
|
+
assert.equal(validateContract("adoption-plan", plan).valid, true);
|
|
36
|
+
for (const target of ["FORGERAIL.md/CLAUDE.md", "forgerail.md", "AGENTS.md", "agents.md", "AGENTS.md/nested.md"]) {
|
|
37
|
+
const invalid = structuredClone(plan);
|
|
38
|
+
invalid.hostSelection.hosts["claude-code"].bindingTarget = target;
|
|
39
|
+
const write = invalid.proposedWrites.find(({ path }) => path === "CLAUDE.md");
|
|
40
|
+
write.path = target;
|
|
41
|
+
write.approvalSha256 = adoptionWriteApprovalDigest(write);
|
|
42
|
+
const result = validateContract("adoption-plan", invalid);
|
|
43
|
+
assert.equal(result.valid, false, target);
|
|
44
|
+
assert.ok(result.errors.some((error) => error.includes("conflict")), result.errors.join("; "));
|
|
45
|
+
}
|
|
46
|
+
for (const writes of [null, {}, [null]]) {
|
|
47
|
+
const invalid = structuredClone(plan);
|
|
48
|
+
invalid.proposedWrites = writes;
|
|
49
|
+
assert.equal(validateContract("adoption-plan", invalid).valid, false);
|
|
50
|
+
}
|
|
51
|
+
assert.equal(existsSync(resolve(workspace, "FORGERAIL.md")), false);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("subset plans report existing unselected adoption without granting it writes", (t) => {
|
|
55
|
+
const workspace = temporary(t, "forgerail-closeout-existing-host-");
|
|
56
|
+
const path = resolve(workspace, "AGENTS.md");
|
|
57
|
+
const content = "<!-- forgerail:binding:codex:v1:start -->\nExisting standalone rules.\n<!-- forgerail:binding:codex:v1:end -->\n";
|
|
58
|
+
writeFileSync(path, content);
|
|
59
|
+
const plan = planAdoption(root, workspace, ["claude-code"]);
|
|
60
|
+
assert.equal(plan.currentLevel, "lightweight-adoption");
|
|
61
|
+
assert.ok(plan.evidence.some((value) => value.includes("Unselected managed binding retained: AGENTS.md")));
|
|
62
|
+
assert.deepEqual(plan.proposedWrites.map(({ path }) => path), ["FORGERAIL.md", "CLAUDE.md"]);
|
|
63
|
+
assert.equal(readFileSync(path, "utf8"), content);
|
|
64
|
+
const noChange = planAdoption(root, workspace, ["claude-code"], "plugin-only");
|
|
65
|
+
assert.equal(noChange.strategy, "no-change");
|
|
66
|
+
assert.equal(noChange.currentLevel, "lightweight-adoption");
|
|
67
|
+
assert.deepEqual(noChange.proposedWrites, []);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("unsafe unselected bindings remain non-blocking and unavailable", (t) => {
|
|
71
|
+
const workspace = temporary(t, "forgerail-closeout-unsafe-host-");
|
|
72
|
+
const outside = temporary(t, "forgerail-closeout-outside-");
|
|
73
|
+
writeFileSync(resolve(outside, "CLAUDE.md"), "<!-- forgerail:binding:claude-code:v1:start -->\n");
|
|
74
|
+
symlinkSync(resolve(outside, "CLAUDE.md"), resolve(workspace, "CLAUDE.md"));
|
|
75
|
+
const plan = planAdoption(root, workspace, ["codex"]);
|
|
76
|
+
assert.equal(plan.currentLevel, "plugin-only");
|
|
77
|
+
assert.ok(plan.evidence.some((value) => value.includes("Unselected binding unavailable: CLAUDE.md")));
|
|
78
|
+
assert.equal(plan.evidence.some((value) => value.includes(outside)), false);
|
|
79
|
+
assert.deepEqual(plan.proposedWrites.map(({ path }) => path), ["AGENTS.md"]);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("thin templates contain an exact shared-contract reference inside their managed block", (t) => {
|
|
83
|
+
const plugin = registry(t);
|
|
84
|
+
const adapter = JSON.parse(readFileSync(resolve(plugin, "adapters/claude-code.json"), "utf8"));
|
|
85
|
+
const path = resolve(plugin, "templates", adapter.bindingTemplates["thin-reference"]);
|
|
86
|
+
const start = `<!-- ${adapter.managedMarker}:start -->`;
|
|
87
|
+
const end = `<!-- ${adapter.managedMarker}:end -->`;
|
|
88
|
+
for (const content of [
|
|
89
|
+
`${start}\nUnrelated instructions.\n${end}`,
|
|
90
|
+
`Follow FORGERAIL.md.\n${start}\nUnrelated instructions.\n${end}`,
|
|
91
|
+
`${start}\nFollow NOTFORGERAIL.md.\n${end}`,
|
|
92
|
+
]) {
|
|
93
|
+
writeFileSync(path, content);
|
|
94
|
+
const result = loadHostAdapters(plugin);
|
|
95
|
+
assert.equal(result.valid, false);
|
|
96
|
+
assert.ok(result.errors.some((error) => error.includes("shared contract reference")), result.errors.join("; "));
|
|
97
|
+
}
|
|
98
|
+
writeFileSync(path, `${start}\nFollow \`FORGERAIL.md\`.\n${end}\n`);
|
|
99
|
+
assert.equal(loadHostAdapters(plugin).valid, true);
|
|
100
|
+
});
|
|
@@ -54,8 +54,8 @@ const result = {
|
|
|
54
54
|
approvalStatus: candidate.approval.status,
|
|
55
55
|
product: manifest.name,
|
|
56
56
|
pluginVersion: manifest.version,
|
|
57
|
-
privateBaseline: "
|
|
58
|
-
sourceDate: "2026-08-
|
|
57
|
+
privateBaseline: "ebf1851300b549c97cd909cbe7354c32661f5063",
|
|
58
|
+
sourceDate: "2026-08-31",
|
|
59
59
|
inventory,
|
|
60
60
|
contentDigest: sha256(JSON.stringify(inventory)),
|
|
61
61
|
unresolvedGates: candidate.unresolvedGates,
|
|
@@ -5,7 +5,7 @@ import { createHash } from "node:crypto";
|
|
|
5
5
|
import { cpSync, existsSync, mkdtempSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
7
|
import { resolve } from "node:path";
|
|
8
|
-
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
9
9
|
|
|
10
10
|
const root = resolve(fileURLToPath(new URL("..", import.meta.url)));
|
|
11
11
|
const base = mkdtempSync(resolve(tmpdir(), "forgerail-consumer-"));
|
|
@@ -48,20 +48,6 @@ function snapshot(path) {
|
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
function renderApprovedWrite(write) {
|
|
52
|
-
const targetPath = resolve(target, write.path);
|
|
53
|
-
const prior = existsSync(targetPath) ? readFileSync(targetPath, "utf8") : "";
|
|
54
|
-
if (write.operation === "create") return write.content;
|
|
55
|
-
if (sha256(prior) !== write.baseSha256) throw new Error(`adoption base digest drifted for ${write.path}`);
|
|
56
|
-
if (write.operation === "append-managed-block") return `${prior.replace(/\s*$/, "")}\n\n${write.content}`;
|
|
57
|
-
const start = `<!-- ${write.managedMarker}:start -->`;
|
|
58
|
-
const end = `<!-- ${write.managedMarker}:end -->`;
|
|
59
|
-
const startIndex = prior.indexOf(start);
|
|
60
|
-
const endIndex = prior.indexOf(end, startIndex);
|
|
61
|
-
if (startIndex < 0 || endIndex < 0) throw new Error(`adoption managed block is missing for ${write.path}`);
|
|
62
|
-
return `${prior.slice(0, startIndex)}${write.content}${prior.slice(endIndex + end.length)}`;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
51
|
const priorPack = JSON.parse(run("npm", ["pack", priorSource, "--json"], base))[0];
|
|
66
52
|
const priorTarball = resolve(base, priorPack.filename);
|
|
67
53
|
run("npm", ["install", "--ignore-scripts", "--no-audit", "--no-fund", priorTarball]);
|
|
@@ -77,9 +63,12 @@ const after = JSON.stringify(snapshot(target));
|
|
|
77
63
|
const adoptionPlan = JSON.parse(run(cli, ["adoption-plan", "--workspace", target, "--host", "codex"]));
|
|
78
64
|
const afterPlan = JSON.stringify(snapshot(target));
|
|
79
65
|
const proposed = adoptionPlan.proposedWrites[0];
|
|
80
|
-
const
|
|
81
|
-
|
|
66
|
+
const adoptionLibrary = await import(pathToFileURL(resolve(installedPackageRoot, "scripts/lib/adoption.mjs")).href);
|
|
67
|
+
const approvedContent = adoptionLibrary.renderProposedWrite(target, proposed);
|
|
68
|
+
const approvedWriteDigest = proposed.approvalSha256;
|
|
69
|
+
const appliedWrite = adoptionLibrary.applyApprovedAdoptionWrite(target, proposed, approvedWriteDigest);
|
|
82
70
|
const approvedDigest = sha256(approvedContent);
|
|
71
|
+
if (appliedWrite.contentSha256 !== approvedDigest) throw new Error("approved adoption write digest mismatch");
|
|
83
72
|
const discoveredSkills = firstValidation.skills;
|
|
84
73
|
const bindingReceipt = {
|
|
85
74
|
schemaVersion: "1.0",
|
|
@@ -112,6 +101,7 @@ run("npm", ["install", "--ignore-scripts", "--no-audit", "--no-fund", priorTarba
|
|
|
112
101
|
const rolledBack = JSON.parse(readFileSync(resolve(installedPackageRoot, "package.json"), "utf8")).version === "0.1.0-alpha.0";
|
|
113
102
|
run("npm", ["install", "--ignore-scripts", "--no-audit", "--no-fund", tarball]);
|
|
114
103
|
const reinstallValidation = JSON.parse(run(cli, ["validate"]));
|
|
104
|
+
const installedIntegrity = JSON.parse(run("npm", ["run", "test:integrity", "--silent"], installedPackageRoot));
|
|
115
105
|
run("npm", ["uninstall", "--no-audit", "--no-fund", packageName]);
|
|
116
106
|
|
|
117
107
|
const result = {
|
|
@@ -133,11 +123,14 @@ const result = {
|
|
|
133
123
|
noPersistedGovernance: !existsSync(resolve(target, ".forgerail")),
|
|
134
124
|
launch: launch.valid && launch.launch.executionOwner === "host-agent",
|
|
135
125
|
reinstall: reinstallValidation.valid,
|
|
126
|
+
installedIntegrity: installedIntegrity.valid
|
|
127
|
+
&& installedIntegrity.assertions.includes("source-only-bundle-regressions-not-installed")
|
|
128
|
+
&& installedIntegrity.assertions.includes("source-only-shadow-regressions-not-installed"),
|
|
136
129
|
upgrade: upgraded,
|
|
137
130
|
rollback: rolledBack,
|
|
138
131
|
uninstall: !existsSync(installedPackageRoot),
|
|
139
132
|
disposableRoot: "[disposable]",
|
|
140
133
|
};
|
|
141
|
-
result.passed = result.priorInstall && result.install && result.binaryShim && result.diagnosis && result.targetUnchangedByDiagnosis && result.adoptionPlan && result.targetUnchangedByPlanner && result.explicitApprovedWrite && result.equivalentNewTaskDiscovery && result.bindingReceipt && result.noPersistedGovernance && result.launch && result.upgrade && result.rollback && result.reinstall && result.uninstall;
|
|
134
|
+
result.passed = result.priorInstall && result.install && result.binaryShim && result.diagnosis && result.targetUnchangedByDiagnosis && result.adoptionPlan && result.targetUnchangedByPlanner && result.explicitApprovedWrite && result.equivalentNewTaskDiscovery && result.bindingReceipt && result.noPersistedGovernance && result.launch && result.upgrade && result.rollback && result.reinstall && result.installedIntegrity && result.uninstall;
|
|
142
135
|
console.log(JSON.stringify(result, null, 2));
|
|
143
136
|
if (!result.passed) process.exitCode = 1;
|
|
@@ -5,35 +5,44 @@
|
|
|
5
5
|
"currentLevel": "plugin-only",
|
|
6
6
|
"proposedLevel": "lightweight-adoption",
|
|
7
7
|
"strategy": "shared-contract-with-thin-bindings",
|
|
8
|
+
"hostSelection": {
|
|
9
|
+
"mode": "explicit",
|
|
10
|
+
"hosts": {
|
|
11
|
+
"codex": { "status": "supported", "bindingTarget": "AGENTS.md", "verificationMode": "new-task-discovery" },
|
|
12
|
+
"claude-code": { "status": "profile-only", "bindingTarget": "CLAUDE.md", "verificationMode": "profile-only" }
|
|
13
|
+
}
|
|
14
|
+
},
|
|
8
15
|
"evidence": ["Codex and Claude Code were explicitly selected."],
|
|
9
|
-
"hosts": [
|
|
10
|
-
{ "adapterId": "codex", "status": "supported", "bindingTarget": "AGENTS.md", "verificationMode": "new-task-discovery" },
|
|
11
|
-
{ "adapterId": "claude-code", "status": "profile-only", "bindingTarget": "CLAUDE.md", "verificationMode": "profile-only" }
|
|
12
|
-
],
|
|
13
16
|
"proposedWrites": [
|
|
14
17
|
{
|
|
18
|
+
"workspaceSha256": "f16d05ec6b29248d2c61adb1e9263f78e4f7bace1b955014a2d17872cfe4064d",
|
|
15
19
|
"path": "FORGERAIL.md",
|
|
16
20
|
"operation": "create",
|
|
17
21
|
"baseSha256": null,
|
|
18
22
|
"contentSha256": "ebba8da7ae587252193419a647f0d8e6898074899158b4f441df573351c9de7c",
|
|
19
23
|
"content": "<!-- forgerail:adoption-contract:v1:start -->\nShared contract.\n<!-- forgerail:adoption-contract:v1:end -->\n",
|
|
20
|
-
"managedMarker": "forgerail:adoption-contract:v1"
|
|
24
|
+
"managedMarker": "forgerail:adoption-contract:v1",
|
|
25
|
+
"approvalSha256": "c074e0b035d4dd68ac597b1ebedc7b58f29b3b3ba98d4cf33de49d437ed27fe0"
|
|
21
26
|
},
|
|
22
27
|
{
|
|
28
|
+
"workspaceSha256": "f16d05ec6b29248d2c61adb1e9263f78e4f7bace1b955014a2d17872cfe4064d",
|
|
23
29
|
"path": "AGENTS.md",
|
|
24
30
|
"operation": "create",
|
|
25
31
|
"baseSha256": null,
|
|
26
32
|
"contentSha256": "989f89220b826a5f355083497552db142fcf1f19cd299febf22b037494c71645",
|
|
27
33
|
"content": "<!-- forgerail:binding:codex:v1:start -->\nRead FORGERAIL.md.\n<!-- forgerail:binding:codex:v1:end -->\n",
|
|
28
|
-
"managedMarker": "forgerail:binding:codex:v1"
|
|
34
|
+
"managedMarker": "forgerail:binding:codex:v1",
|
|
35
|
+
"approvalSha256": "b2f76b3d4f6730ebac4771e7fcecb1969df6ec62a05c2f6bcc441e8e8aa2741a"
|
|
29
36
|
},
|
|
30
37
|
{
|
|
38
|
+
"workspaceSha256": "f16d05ec6b29248d2c61adb1e9263f78e4f7bace1b955014a2d17872cfe4064d",
|
|
31
39
|
"path": "CLAUDE.md",
|
|
32
40
|
"operation": "create",
|
|
33
41
|
"baseSha256": null,
|
|
34
42
|
"contentSha256": "e72fdc3bd532202c2b9c52095e8e20050d428a150285c3b4f1cfce040d3a481c",
|
|
35
43
|
"content": "<!-- forgerail:binding:claude-code:v1:start -->\nRead FORGERAIL.md.\n<!-- forgerail:binding:claude-code:v1:end -->\n",
|
|
36
|
-
"managedMarker": "forgerail:binding:claude-code:v1"
|
|
44
|
+
"managedMarker": "forgerail:binding:claude-code:v1",
|
|
45
|
+
"approvalSha256": "cc472c070fcc287f1d226600e316b0cb0cfbf8303dfd9ed44dc2b6b98b9d47ff"
|
|
37
46
|
}
|
|
38
47
|
],
|
|
39
48
|
"requiredConfirmation": true,
|
|
@@ -5,10 +5,13 @@
|
|
|
5
5
|
"currentLevel": "plugin-only",
|
|
6
6
|
"proposedLevel": "lightweight-adoption",
|
|
7
7
|
"strategy": "single-host-managed-block",
|
|
8
|
+
"hostSelection": {
|
|
9
|
+
"mode": "explicit",
|
|
10
|
+
"hosts": {
|
|
11
|
+
"codex": { "status": "supported", "bindingTarget": "AGENTS.md", "verificationMode": "new-task-discovery" }
|
|
12
|
+
}
|
|
13
|
+
},
|
|
8
14
|
"evidence": ["Codex was selected."],
|
|
9
|
-
"hosts": [
|
|
10
|
-
{ "adapterId": "codex", "status": "supported", "bindingTarget": "AGENTS.md", "verificationMode": "new-task-discovery" }
|
|
11
|
-
],
|
|
12
15
|
"proposedWrites": [],
|
|
13
16
|
"requiredConfirmation": true,
|
|
14
17
|
"verification": ["Start a new task."],
|
|
@@ -5,18 +5,23 @@
|
|
|
5
5
|
"currentLevel": "plugin-only",
|
|
6
6
|
"proposedLevel": "lightweight-adoption",
|
|
7
7
|
"strategy": "single-host-managed-block",
|
|
8
|
+
"hostSelection": {
|
|
9
|
+
"mode": "explicit",
|
|
10
|
+
"hosts": {
|
|
11
|
+
"codex": { "status": "supported", "bindingTarget": "AGENTS.md", "verificationMode": "new-task-discovery" }
|
|
12
|
+
}
|
|
13
|
+
},
|
|
8
14
|
"evidence": ["Codex was explicitly selected."],
|
|
9
|
-
"hosts": [
|
|
10
|
-
{ "adapterId": "codex", "status": "supported", "bindingTarget": "AGENTS.md", "verificationMode": "new-task-discovery" }
|
|
11
|
-
],
|
|
12
15
|
"proposedWrites": [
|
|
13
16
|
{
|
|
17
|
+
"workspaceSha256": "f16d05ec6b29248d2c61adb1e9263f78e4f7bace1b955014a2d17872cfe4064d",
|
|
14
18
|
"path": "AGENTS.md",
|
|
15
19
|
"operation": "create",
|
|
16
20
|
"baseSha256": null,
|
|
17
21
|
"contentSha256": "0bccefea0979ec3ad7b80555d1da43a793ab710417b9d4541aa2a15be9ad0810",
|
|
18
22
|
"content": "<!-- forgerail:binding:codex:v1:start -->\nUse ForgeRail.\n<!-- forgerail:binding:codex:v1:end -->\n",
|
|
19
|
-
"managedMarker": "forgerail:binding:codex:v1"
|
|
23
|
+
"managedMarker": "forgerail:binding:codex:v1",
|
|
24
|
+
"approvalSha256": "4a3ac2446e5f1ec0fe3273c106b9ca6aead4aaa4c355ae722ee9921fae9148da"
|
|
20
25
|
}
|
|
21
26
|
],
|
|
22
27
|
"requiredConfirmation": true,
|
|
@@ -18,12 +18,11 @@
|
|
|
18
18
|
"status": "observed"
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
|
-
"packs":
|
|
22
|
-
{
|
|
23
|
-
"id": "workspace-health-review",
|
|
21
|
+
"packs": {
|
|
22
|
+
"workspace-health-review": {
|
|
24
23
|
"state": "available",
|
|
25
24
|
"reason": "Installed with ForgeRail but not required for this task."
|
|
26
25
|
}
|
|
27
|
-
|
|
26
|
+
},
|
|
28
27
|
"conflicts": []
|
|
29
28
|
}
|
|
@@ -6,7 +6,12 @@
|
|
|
6
6
|
"instructionDiscovery": "task-start",
|
|
7
7
|
"skillDiscovery": "agent-skills",
|
|
8
8
|
"bindingTarget": "CLAUDE.md",
|
|
9
|
-
"
|
|
9
|
+
"detectionTargets": ["CLAUDE.md", ".claude"],
|
|
10
|
+
"bindingModes": ["thin-reference"],
|
|
11
|
+
"bindingTemplates": {
|
|
12
|
+
"thin-reference": "bindings/claude-code-thin.md"
|
|
13
|
+
},
|
|
14
|
+
"unmanagedBindingPolicy": "append-managed-block",
|
|
10
15
|
"managedMarker": "forgerail:binding:claude-code:v1",
|
|
11
16
|
"activationBoundary": "host-specific-verification-required",
|
|
12
17
|
"verification": { "mode": "profile-only", "expectedSkills": [] },
|
|
@@ -6,7 +6,13 @@
|
|
|
6
6
|
"instructionDiscovery": "task-start",
|
|
7
7
|
"skillDiscovery": "agent-plugin-skills",
|
|
8
8
|
"bindingTarget": "AGENTS.md",
|
|
9
|
+
"detectionTargets": ["AGENTS.md"],
|
|
9
10
|
"bindingModes": ["managed-block", "thin-reference"],
|
|
11
|
+
"bindingTemplates": {
|
|
12
|
+
"managed-block": "bindings/codex-compact.md",
|
|
13
|
+
"thin-reference": "bindings/codex-thin.md"
|
|
14
|
+
},
|
|
15
|
+
"unmanagedBindingPolicy": "append-managed-block",
|
|
10
16
|
"managedMarker": "forgerail:binding:codex:v1",
|
|
11
17
|
"activationBoundary": "new-task-required",
|
|
12
18
|
"verification": {
|
|
@@ -6,7 +6,12 @@
|
|
|
6
6
|
"instructionDiscovery": "rules",
|
|
7
7
|
"skillDiscovery": "unknown",
|
|
8
8
|
"bindingTarget": ".cursor/rules/forgerail.mdc",
|
|
9
|
+
"detectionTargets": [".cursor"],
|
|
9
10
|
"bindingModes": ["thin-reference"],
|
|
11
|
+
"bindingTemplates": {
|
|
12
|
+
"thin-reference": "bindings/cursor-thin.mdc"
|
|
13
|
+
},
|
|
14
|
+
"unmanagedBindingPolicy": "reject",
|
|
10
15
|
"managedMarker": "forgerail:binding:cursor:v1",
|
|
11
16
|
"activationBoundary": "host-specific-verification-required",
|
|
12
17
|
"verification": { "mode": "profile-only", "expectedSkills": [] },
|
|
@@ -6,7 +6,12 @@
|
|
|
6
6
|
"instructionDiscovery": "task-start",
|
|
7
7
|
"skillDiscovery": "agent-skills",
|
|
8
8
|
"bindingTarget": "CLAUDE.md",
|
|
9
|
-
"
|
|
9
|
+
"detectionTargets": ["CLAUDE.md", ".claude"],
|
|
10
|
+
"bindingModes": ["thin-reference"],
|
|
11
|
+
"bindingTemplates": {
|
|
12
|
+
"thin-reference": "bindings/claude-code-thin.md"
|
|
13
|
+
},
|
|
14
|
+
"unmanagedBindingPolicy": "append-managed-block",
|
|
10
15
|
"managedMarker": "forgerail:binding:claude-code:v1",
|
|
11
16
|
"activationBoundary": "host-specific-verification-required",
|
|
12
17
|
"verification": { "mode": "profile-only", "expectedSkills": [] },
|
|
@@ -8,11 +8,15 @@
|
|
|
8
8
|
"ownerWorkspace": "fixture-workspace",
|
|
9
9
|
"allowedOperations": ["read files", "edit documentation", "run local validation"],
|
|
10
10
|
"prohibitedOperations": ["push", "merge", "tag", "release"],
|
|
11
|
-
"packs":
|
|
11
|
+
"packs": {},
|
|
12
12
|
"approvalGates": ["remote-integration-approval", "release-approval"],
|
|
13
13
|
"validation": ["git diff --check passes"],
|
|
14
14
|
"returnContract": "forgerail-return-receipt-v1"
|
|
15
15
|
},
|
|
16
|
+
"effectiveProfile": {
|
|
17
|
+
"digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
18
|
+
},
|
|
19
|
+
"effectivePackManifests": {},
|
|
16
20
|
"effectiveRuleSources": ["AGENTS.md", "ForgeRail Core"],
|
|
17
21
|
"hostAgent": "Codex",
|
|
18
22
|
"executionOwner": "forgerail"
|
|
@@ -8,11 +8,15 @@
|
|
|
8
8
|
"ownerWorkspace": "fixture-workspace",
|
|
9
9
|
"allowedOperations": ["read files", "edit documentation", "run local validation"],
|
|
10
10
|
"prohibitedOperations": ["push", "merge", "tag", "release"],
|
|
11
|
-
"packs":
|
|
11
|
+
"packs": {},
|
|
12
12
|
"approvalGates": ["remote-integration-approval", "release-approval"],
|
|
13
13
|
"validation": ["git diff --check passes"],
|
|
14
14
|
"returnContract": "forgerail-return-receipt-v1"
|
|
15
15
|
},
|
|
16
|
+
"effectiveProfile": {
|
|
17
|
+
"digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
18
|
+
},
|
|
19
|
+
"effectivePackManifests": {},
|
|
16
20
|
"effectiveRuleSources": ["AGENTS.md", "ForgeRail Core"],
|
|
17
21
|
"hostAgent": "Codex",
|
|
18
22
|
"executionOwner": "host-agent"
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "1.0",
|
|
3
|
+
"cases": [
|
|
4
|
+
{
|
|
5
|
+
"id": "current-release-and-install",
|
|
6
|
+
"kind": "release-text",
|
|
7
|
+
"content": "Current public prerelease 0.1.0-alpha.4. codex plugin marketplace add echopath-labs/forgerail --ref v0.1.0-alpha.4",
|
|
8
|
+
"expectedErrors": []
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"id": "stale-alpha2-install",
|
|
12
|
+
"kind": "release-text",
|
|
13
|
+
"content": "Install the current v0.1.0-alpha.2 release.",
|
|
14
|
+
"expectedErrors": ["stale-release"]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"id": "broken-relative-link",
|
|
18
|
+
"kind": "markdown-link",
|
|
19
|
+
"content": "Read [missing](does-not-exist.md).",
|
|
20
|
+
"expectedErrors": ["broken-link"]
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"id": "valid-issue-form",
|
|
24
|
+
"kind": "issue-form",
|
|
25
|
+
"content": "name: Bug report\ndescription: Report a problem.\nbody: []\n",
|
|
26
|
+
"expectedErrors": []
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "missing-issue-description",
|
|
30
|
+
"kind": "issue-form",
|
|
31
|
+
"content": "name: Bug report\nbody: []\n",
|
|
32
|
+
"expectedErrors": ["invalid-issue-metadata"]
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "stale-alpha3-issue-version",
|
|
36
|
+
"kind": "issue-form",
|
|
37
|
+
"content": "name: Documentation issue\ndescription: Report stale docs.\nbody:\n - label: I checked the current alpha.3 documentation.\n",
|
|
38
|
+
"expectedErrors": ["stale-issue-version"]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"id": "private-path-leak",
|
|
42
|
+
"kind": "public-text",
|
|
43
|
+
"content": "Continue in __PRIVATE_USER_PATH__.",
|
|
44
|
+
"expectedErrors": ["private-path"]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"id": "private-process-reference-leak",
|
|
48
|
+
"kind": "public-text",
|
|
49
|
+
"content": "Canonical architecture change: openspec/changes/private-control-system/",
|
|
50
|
+
"expectedErrors": ["private-process-reference"]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "project-node-requirement",
|
|
54
|
+
"kind": "no-node-claim",
|
|
55
|
+
"content": "Before using the Plugin, initialize package.json in your project and install Node.js.",
|
|
56
|
+
"expectedErrors": ["project-node-required"]
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"id": "plugin-only-no-node",
|
|
60
|
+
"kind": "no-node-claim",
|
|
61
|
+
"content": "Plugin Only does not require Node.js, package.json, node_modules, or .forgerail/ in the target project.",
|
|
62
|
+
"expectedErrors": []
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
}
|