release-skill 0.1.5 → 0.1.7
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/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +4 -4
- package/CHANGELOG.md +108 -0
- package/INSTALL.md +81 -1
- package/INSTALL.zh-CN.md +69 -1
- package/LICENSE +1 -0
- package/NOTICE +10 -0
- package/README.md +181 -8
- package/README.zh-CN.md +153 -8
- package/adapters/claude/.claude-plugin/marketplace.json +3 -3
- package/adapters/claude/.claude-plugin/plugin.json +2 -2
- package/adapters/claude/bin/release-skill.bundle.mjs +14182 -10017
- package/adapters/claude/bin/release-skill.mjs +24 -4
- package/adapters/claude/native/safe-write/binding.gyp +2 -1
- package/adapters/claude/native/safe-write/prebuilds/darwin-arm64/safe_write.node +0 -0
- package/adapters/claude/native/safe-write/prebuilds.json +1 -1
- package/adapters/claude/schemas/.render-manifest.json +10 -10
- package/adapters/claude/schemas/release-project.schema.json +118 -0
- package/adapters/claude/skills/release-help/SKILL.md +21 -0
- package/adapters/claude/skills/release-prepare/SKILL.md +17 -6
- package/adapters/claude/skills/release-publish/SKILL.md +3 -1
- package/adapters/claude/skills/release-reconcile/SKILL.md +1 -1
- package/adapters/codex/.codex-plugin/plugin.json +4 -4
- package/adapters/codex/bin/release-skill.bundle.mjs +14182 -10017
- package/adapters/codex/bin/release-skill.mjs +24 -4
- package/adapters/codex/native/safe-write/binding.gyp +2 -1
- package/adapters/codex/native/safe-write/prebuilds/darwin-arm64/safe_write.node +0 -0
- package/adapters/codex/native/safe-write/prebuilds.json +1 -1
- package/adapters/codex/schemas/.render-manifest.json +10 -10
- package/adapters/codex/schemas/release-project.schema.json +118 -0
- package/adapters/codex/skills/release-help/SKILL.md +21 -0
- package/adapters/codex/skills/release-prepare/SKILL.md +17 -6
- package/adapters/codex/skills/release-publish/SKILL.md +3 -1
- package/adapters/codex/skills/release-reconcile/SKILL.md +1 -1
- package/bin/release-skill-cli.mjs +163 -4
- package/bin/release-skill.bundle.mjs +14182 -10017
- package/bin/release-skill.mjs +24 -4
- package/native/safe-write/binding.gyp +2 -1
- package/native/safe-write/prebuilds/darwin-arm64/safe_write.node +0 -0
- package/native/safe-write/prebuilds.json +1 -1
- package/package.json +13 -5
- package/references/.render-manifest.json +4 -4
- package/references/02-project-config.md +24 -0
- package/references/05-evidence-and-errors.md +5 -0
- package/schemas/.render-manifest.json +10 -10
- package/schemas/release-project.schema.json +118 -0
- package/scripts/build-bundle.mjs +15 -2
- package/skills/release-help/SKILL.md +21 -0
- package/skills/release-prepare/SKILL.md +17 -6
- package/skills/release-publish/SKILL.md +3 -1
- package/skills/release-reconcile/SKILL.md +1 -1
- package/skills-src/release-help/SKILL.md +21 -0
- package/skills-src/release-prepare/SKILL.md +17 -6
- package/skills-src/release-publish/SKILL.md +3 -1
- package/skills-src/release-reconcile/SKILL.md +1 -1
- package/src/artifacts/transaction-journal.mjs +1126 -105
- package/src/artifacts/transaction.mjs +313 -130
- package/src/commands/docs.mjs +332 -0
- package/src/commands/prepare.mjs +316 -17
- package/src/core/errors.mjs +64 -2
- package/src/core/redact.mjs +206 -0
- package/src/docs/changelog-renderer.mjs +853 -0
- package/src/docs/config.mjs +337 -0
- package/src/docs/notes-loader.mjs +432 -0
- package/src/docs/notes.mjs +553 -0
- package/src/docs/readme-renderer.mjs +647 -0
- package/src/docs/refresh-planner.mjs +542 -0
- package/src/docs/refresh-service.mjs +675 -0
|
@@ -23,12 +23,32 @@ try {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
if (!bundleExists) {
|
|
26
|
+
// Fail closed with static text only: never interpolate bundlePath (or any
|
|
27
|
+
// other machine-specific value) so a copied/installed launcher cannot leak
|
|
28
|
+
// absolute paths, usernames, or host layout on stdout/stderr. This branch
|
|
29
|
+
// is self-contained by design — it must not import src/* or rely on the
|
|
30
|
+
// missing bundle's redaction helpers.
|
|
26
31
|
console.error(
|
|
27
|
-
`Error: release-skill bundle not found
|
|
28
|
-
`The bundle is required for installed-plugin execution.\n` +
|
|
29
|
-
`Reinstall the plugin or run 'node scripts/build-bundle.mjs' in
|
|
32
|
+
`Error: release-skill bundle not found (release-skill.bundle.mjs).\n` +
|
|
33
|
+
`The self-contained bundle is required for installed-plugin execution.\n` +
|
|
34
|
+
`Reinstall the plugin, or run 'node scripts/build-bundle.mjs' in a source checkout to rebuild it.`,
|
|
30
35
|
);
|
|
31
36
|
process.exit(1);
|
|
32
37
|
}
|
|
33
38
|
|
|
34
|
-
|
|
39
|
+
// The bundle owns the command lifecycle: its entry awaits command completion
|
|
40
|
+
// and exits with the real business exit code (success, business errors,
|
|
41
|
+
// handled async rejections, unknown commands). The launcher only guards the
|
|
42
|
+
// load itself: if the bundle cannot be evaluated (corrupt or incompatible
|
|
43
|
+
// build), fail closed with static text only — module-load failures carry
|
|
44
|
+
// absolute paths in their messages, so the failure is never interpolated.
|
|
45
|
+
try {
|
|
46
|
+
await import(bundlePath);
|
|
47
|
+
} catch {
|
|
48
|
+
console.error(
|
|
49
|
+
`Error: release-skill bundle failed to load (release-skill.bundle.mjs).\n` +
|
|
50
|
+
`The self-contained bundle is required for installed-plugin execution.\n` +
|
|
51
|
+
`Reinstall the plugin, or run 'node scripts/build-bundle.mjs' in a source checkout to rebuild it.`,
|
|
52
|
+
);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
Binary file
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"prebuilds": {
|
|
4
4
|
"darwin-arm64": {
|
|
5
5
|
"path": "prebuilds/darwin-arm64/safe_write.node",
|
|
6
|
-
"sha256": "
|
|
6
|
+
"sha256": "f5ffc367a1c49fed812dc430246cd8b07fb722653cc9738acabcedb17d6bab67",
|
|
7
7
|
"exports": [
|
|
8
8
|
"openRoot",
|
|
9
9
|
"openDir",
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"source": "schemas",
|
|
3
3
|
"files": {
|
|
4
4
|
"approval-record.schema.json": {
|
|
5
|
-
"digest": "
|
|
6
|
-
"bytes":
|
|
5
|
+
"digest": "2ddac883435110e2c036cfadb00ce0c4d347a97d674b2c4ea9fcda3d72f81b8b",
|
|
6
|
+
"bytes": 3135
|
|
7
7
|
},
|
|
8
8
|
"artifact-lock.schema.json": {
|
|
9
9
|
"digest": "6a5f4c826540b849571f173118bcbd778dfaceb4133034036ae565c3e9e353e5",
|
|
@@ -14,24 +14,24 @@
|
|
|
14
14
|
"bytes": 1546
|
|
15
15
|
},
|
|
16
16
|
"artifact-policy.schema.json": {
|
|
17
|
-
"digest": "
|
|
18
|
-
"bytes":
|
|
17
|
+
"digest": "86c9cab024ebe9c7f8ec3358fcc12d05b8ed246ab59da2b0acc6a517f31274ea",
|
|
18
|
+
"bytes": 2488
|
|
19
19
|
},
|
|
20
20
|
"evidence-event.schema.json": {
|
|
21
21
|
"digest": "b7e14522a5aba818cab191545f23d2323be0e4c80566e9d3e4da576103a00490",
|
|
22
22
|
"bytes": 2390
|
|
23
23
|
},
|
|
24
24
|
"release-plan.schema.json": {
|
|
25
|
-
"digest": "
|
|
26
|
-
"bytes":
|
|
25
|
+
"digest": "13baa16d9a762cc04c7f28fc9d31ed81d381008c71eb044f4e97de252b4cc324",
|
|
26
|
+
"bytes": 22942
|
|
27
27
|
},
|
|
28
28
|
"release-project.schema.json": {
|
|
29
|
-
"digest": "
|
|
30
|
-
"bytes":
|
|
29
|
+
"digest": "4e46d6b1956a14d01471e7cf1cde3c0a6971c4f8844603fedd2a52f3535a43b6",
|
|
30
|
+
"bytes": 24167
|
|
31
31
|
},
|
|
32
32
|
"release-run.schema.json": {
|
|
33
|
-
"digest": "
|
|
34
|
-
"bytes":
|
|
33
|
+
"digest": "74f087f8815f00997c6ad3bac033660b9b69de226a6f5c48a96194dba0fa872c",
|
|
34
|
+
"bytes": 8917
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
}
|
|
@@ -445,6 +445,9 @@
|
|
|
445
445
|
}
|
|
446
446
|
]
|
|
447
447
|
}
|
|
448
|
+
},
|
|
449
|
+
"releaseDocuments": {
|
|
450
|
+
"$ref": "#/definitions/releaseDocuments"
|
|
448
451
|
}
|
|
449
452
|
},
|
|
450
453
|
"allOf": [
|
|
@@ -634,6 +637,121 @@
|
|
|
634
637
|
}
|
|
635
638
|
}
|
|
636
639
|
},
|
|
640
|
+
"releaseDocuments": {
|
|
641
|
+
"type": "object",
|
|
642
|
+
"description": "Optional configuration-driven multilingual release-document refresh for one release unit. Absence preserves legacy behaviour. Lexical layer only; runtime path, locale-membership and collision checks apply additionally.",
|
|
643
|
+
"required": [
|
|
644
|
+
"notesSource",
|
|
645
|
+
"locales",
|
|
646
|
+
"changelogs",
|
|
647
|
+
"readmes"
|
|
648
|
+
],
|
|
649
|
+
"additionalProperties": false,
|
|
650
|
+
"properties": {
|
|
651
|
+
"notesSource": {
|
|
652
|
+
"type": "string",
|
|
653
|
+
"minLength": 1,
|
|
654
|
+
"pattern": "^(?!/)(?!\\.\\/)(?!.*\\\\)(?!.*:)(?!\\.\\.(/|$))(?!\\.$)(?!.*\\/\\.\\.\\/)(?!.*\\/\\.\\.$)(?!.*\\/\\.\\/)(?!.*\\/\\/)(?!.*\\0)(?!.*\\/\\.$)(?!.*\\/$)(?!.*\\{(?!version\\}))(?!.*(?<!\\{version)\\}).+\\.(?:yaml|yml|json)$"
|
|
655
|
+
},
|
|
656
|
+
"locales": {
|
|
657
|
+
"type": "array",
|
|
658
|
+
"minItems": 1,
|
|
659
|
+
"uniqueItems": true,
|
|
660
|
+
"items": {
|
|
661
|
+
"type": "string",
|
|
662
|
+
"pattern": "^[a-zA-Z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"
|
|
663
|
+
}
|
|
664
|
+
},
|
|
665
|
+
"changelogs": {
|
|
666
|
+
"type": "array",
|
|
667
|
+
"minItems": 1,
|
|
668
|
+
"items": {
|
|
669
|
+
"$ref": "#/definitions/releaseDocumentsChangelog"
|
|
670
|
+
}
|
|
671
|
+
},
|
|
672
|
+
"readmes": {
|
|
673
|
+
"type": "array",
|
|
674
|
+
"minItems": 1,
|
|
675
|
+
"items": {
|
|
676
|
+
"$ref": "#/definitions/releaseDocumentsReadme"
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
},
|
|
681
|
+
"releaseDocumentsChangelog": {
|
|
682
|
+
"type": "object",
|
|
683
|
+
"required": [
|
|
684
|
+
"path",
|
|
685
|
+
"locale"
|
|
686
|
+
],
|
|
687
|
+
"additionalProperties": false,
|
|
688
|
+
"properties": {
|
|
689
|
+
"path": {
|
|
690
|
+
"type": "string",
|
|
691
|
+
"minLength": 1,
|
|
692
|
+
"pattern": "^(?!/)(?!\\.\\/)(?!.*\\\\)(?!.*:)(?!\\.\\.(/|$))(?!\\.$)(?!.*\\/\\.\\.\\/)(?!.*\\/\\.\\.$)(?!.*\\/\\.\\/)(?!.*\\/\\/)(?!.*\\0)(?!.*\\/\\.$)(?!.*\\/$).*$"
|
|
693
|
+
},
|
|
694
|
+
"locale": {
|
|
695
|
+
"type": "string",
|
|
696
|
+
"pattern": "^[a-zA-Z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
},
|
|
700
|
+
"releaseDocumentsReadme": {
|
|
701
|
+
"type": "object",
|
|
702
|
+
"required": [
|
|
703
|
+
"path",
|
|
704
|
+
"locale",
|
|
705
|
+
"regions"
|
|
706
|
+
],
|
|
707
|
+
"additionalProperties": false,
|
|
708
|
+
"properties": {
|
|
709
|
+
"path": {
|
|
710
|
+
"type": "string",
|
|
711
|
+
"minLength": 1,
|
|
712
|
+
"pattern": "^(?!/)(?!\\.\\/)(?!.*\\\\)(?!.*:)(?!\\.\\.(/|$))(?!\\.$)(?!.*\\/\\.\\.\\/)(?!.*\\/\\.\\.$)(?!.*\\/\\.\\/)(?!.*\\/\\/)(?!.*\\0)(?!.*\\/\\.$)(?!.*\\/$).*$"
|
|
713
|
+
},
|
|
714
|
+
"locale": {
|
|
715
|
+
"type": "string",
|
|
716
|
+
"pattern": "^[a-zA-Z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"
|
|
717
|
+
},
|
|
718
|
+
"regions": {
|
|
719
|
+
"type": "array",
|
|
720
|
+
"minItems": 1,
|
|
721
|
+
"uniqueItems": true,
|
|
722
|
+
"items": {
|
|
723
|
+
"type": "string",
|
|
724
|
+
"pattern": "^[a-z0-9][a-z0-9._-]*$"
|
|
725
|
+
}
|
|
726
|
+
},
|
|
727
|
+
"versionMarkers": {
|
|
728
|
+
"type": "array",
|
|
729
|
+
"minItems": 1,
|
|
730
|
+
"uniqueItems": true,
|
|
731
|
+
"items": {
|
|
732
|
+
"$ref": "#/definitions/releaseDocumentsVersionMarker"
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
},
|
|
737
|
+
"releaseDocumentsVersionMarker": {
|
|
738
|
+
"type": "object",
|
|
739
|
+
"required": [
|
|
740
|
+
"id",
|
|
741
|
+
"pattern"
|
|
742
|
+
],
|
|
743
|
+
"additionalProperties": false,
|
|
744
|
+
"properties": {
|
|
745
|
+
"id": {
|
|
746
|
+
"type": "string",
|
|
747
|
+
"pattern": "^[a-z0-9][a-z0-9._-]*$"
|
|
748
|
+
},
|
|
749
|
+
"pattern": {
|
|
750
|
+
"type": "string",
|
|
751
|
+
"minLength": 1
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
},
|
|
637
755
|
"verificationGate": {
|
|
638
756
|
"type": "object",
|
|
639
757
|
"required": [
|
|
@@ -42,8 +42,24 @@ description: "Discoverable entry point for release-skill: dependency and environ
|
|
|
42
42
|
node "$RELEASE_SKILL_ENTRY" help --json
|
|
43
43
|
node "$RELEASE_SKILL_ENTRY" setup --root <path> --json
|
|
44
44
|
node "$RELEASE_SKILL_ENTRY" assess --root <path> --offline --json
|
|
45
|
+
# 发布文档刷新:默认只读演练
|
|
46
|
+
node "$RELEASE_SKILL_ENTRY" docs refresh --unit <id> --json
|
|
47
|
+
# 摘要确认后的本地写入(三项绑定缺一不可)
|
|
48
|
+
node "$RELEASE_SKILL_ENTRY" docs refresh --unit <id> \
|
|
49
|
+
--write --confirm-refresh <refreshDigest> --ack-local-document-write --json
|
|
45
50
|
```
|
|
46
51
|
|
|
52
|
+
## 发布文档刷新(docs refresh)
|
|
53
|
+
|
|
54
|
+
发布单元配置 `releaseDocuments` 后,一份结构化双语说明源可确定性刷新 README 受管区域、唯一版本标记的机器值和 CHANGELOG 当前版本受管条目。核心 CLI 不联网、不调用大模型、不自动翻译;只改写声明过的受管区域、版本标记机器值和当前受管条目,区域外字节逐字保留。`prepare` 只检查新鲜度,不写工作树。
|
|
55
|
+
|
|
56
|
+
- **配置**:`releaseDocuments.notesSource`(说明源路径,只允许 `{version}` 占位符与 `.yaml`/`.yml`/`.json` 后缀)、`locales`(如 `[en, zh-CN]`)、`changelogs`(path + locale)、`readmes`(path + locale + `regions` 受管区域 id + `versionMarkers` 版本标记模式)。版本标记模式必须与 README 现有唯一标记精确匹配,`{version}` 代表机器版本值,刷新只替换该值;零次或多次匹配失败关闭。
|
|
57
|
+
- **说明源**:`version` 必须与单元版本精确一致,`date` 为 `YYYY-MM-DD`,每个配置语种恰好出现一次且 `summary`、变更项非空,`security`/`breaking`/`added`/`changed`/`deprecated`/`removed`/`fixed` 至少一个类别含条目。YAML alias、重复键、未知字段和语种回退都失败关闭。
|
|
58
|
+
- **只读演练**:`docs refresh --unit <id> --json` 输出逐文件相对路径、locale、新旧摘要、`version`、`locales`、`inputDigest`、`refreshDigest` 和 `nextCommand.argv`;候选无变化时 `status: "clean"`。
|
|
59
|
+
- **确认写入**:必须同时提供 `--write`、精确 `--confirm-refresh <refreshDigest>` 和 `--ack-local-document-write`,全部目标作为一个事务提交;成功后立即复演必须为 `clean`。
|
|
60
|
+
|
|
61
|
+
**授权边界**:本地发布文档写入授权只覆盖声明的本地文档目标,不是 hook、Git 提交、push、publish 或安装的授权。写入后必须审阅、提交,再重新 prepare。
|
|
62
|
+
|
|
47
63
|
## 故障路由
|
|
48
64
|
|
|
49
65
|
| 场景 | 处理 |
|
|
@@ -57,6 +73,11 @@ node "$RELEASE_SKILL_ENTRY" assess --root <path> --offline --json
|
|
|
57
73
|
| 项目配置不存在 | 路由 `release-setup`,默认只读;不得直接生成或覆盖 README/配置 |
|
|
58
74
|
| assess 失败 | 运行 `node "$RELEASE_SKILL_ENTRY" assess --offline --json` 获取详情 |
|
|
59
75
|
| 请求生产发布 | 已有公开版本先调用 `release-prepare --online --production` 观察 bound 基线;人工审阅后再路由 `release-publish` |
|
|
76
|
+
| RELEASE_DOCS_INVALID | 配置或说明源语义非法(重复键、alias、未知字段、版本漂移等);修正配置或说明源后重新演练 |
|
|
77
|
+
| RELEASE_DOCS_TRANSLATION_MISSING | 配置语种缺失或多余;补齐说明源语种,与 `releaseDocuments.locales` 完全一致,不得回退 |
|
|
78
|
+
| RELEASE_DOCS_CONFLICT | 目标含非受管同版本条目、受管标记损坏或人工冲突;人工修复目标并保留人工修改后重新演练 |
|
|
79
|
+
| RELEASE_DOCS_REFRESH_STALE | 确认绑定后候选已变化;重新演练取得新 `refreshDigest` 再确认写入 |
|
|
80
|
+
| RELEASE_DOCS_STALE | prepare 检测到文档未刷新;按 `docs refresh` → 审阅 → 提交 → 重新 prepare 恢复 |
|
|
60
81
|
|
|
61
82
|
## 后续引导
|
|
62
83
|
|
|
@@ -30,13 +30,16 @@ description: Freeze an immutable release plan with local configuration, document
|
|
|
30
30
|
|
|
31
31
|
**不确定性停止**: 遇到无法确定的配置项或版本冲突时,Agent 必须停止并上报用户。
|
|
32
32
|
|
|
33
|
+
**发布文档新鲜度门**: 配置了 `releaseDocuments` 的单元在 hook 授权门前先执行同一只读规划器:`clean` 继续;`changes` 抛 `RELEASE_DOCS_STALE`,详情列出相对路径、语种、`refreshDigest` 和精确演练/写入参数数组。prepare 只检查、不写工作树。正式 prepare 前先运行只读演练;有变化时向用户展示文件/语种/版本/`refreshDigest`,只有在用户明确授权"本地发布文档写入"后,才执行带 `--write --confirm-refresh <refreshDigest> --ack-local-document-write` 三项绑定的写入,随后运行聚焦校验,要求维护者审阅并提交刷新结果,再重新 prepare。该授权不扩展为 hook、提交、push 或 publish 授权。
|
|
34
|
+
|
|
33
35
|
## 正向执行路径
|
|
34
36
|
|
|
35
37
|
1. 使用插件根相对路径运行 CLI:`CLI="node $RELEASE_SKILL_ENTRY"`
|
|
36
|
-
2.
|
|
37
|
-
3.
|
|
38
|
-
4.
|
|
39
|
-
5.
|
|
38
|
+
2. 配置含 `releaseDocuments` 时,先运行只读演练 `${CLI} docs refresh --unit <id> --json`;`status: "changes"` 时展示逐文件路径/语种/版本/`refreshDigest`,取得"本地发布文档写入"明确授权后才执行 `nextCommand.argv` 写入,审阅并提交刷新结果后再继续;`status: "clean"` 时直接进入 prepare
|
|
39
|
+
3. 运行 `${CLI} prepare --root <path> --offline --json`
|
|
40
|
+
4. 若遇到 hook/gate 授权门失败,分别展示命令和风险,获取授权后只增加实际需要的 `--acknowledge-hook-side-effects` / `--acknowledge-gate-side-effects`
|
|
41
|
+
5. 检查 exit code 0,读取 JSON 返回的 immutable `planPath=plans/<planDigest>.json`,再从该文件读取 `status`、`units`、`externalActions`
|
|
42
|
+
6. 向用户展示 targetVersion、externalActions、planDigest 和 planPath;后续 approve/publish 只能使用该 immutable planPath,等待确认后再 approve
|
|
40
43
|
|
|
41
44
|
若用户明确要求 GitHub+npm 生产发布,加入 `--production`。该模式还会封存独立
|
|
42
45
|
Git commit/tree 和 npm tarball,并把路径、SHA/integrity、branch/tag 写入计划。
|
|
@@ -55,6 +58,11 @@ prepare 后若人工继续修改 README 或任何源文件,应保留修改并
|
|
|
55
58
|
## 确定性脚本调用
|
|
56
59
|
|
|
57
60
|
```bash
|
|
61
|
+
# 发布文档新鲜度:prepare 前只读演练(配置了 releaseDocuments 的单元)
|
|
62
|
+
node "$RELEASE_SKILL_ENTRY" docs refresh --unit <id> --json
|
|
63
|
+
# 仅在用户明确授权“本地发布文档写入”后执行(三项绑定缺一不可)
|
|
64
|
+
node "$RELEASE_SKILL_ENTRY" docs refresh --unit <id> \
|
|
65
|
+
--write --confirm-refresh <refreshDigest> --ack-local-document-write --json
|
|
58
66
|
node "$RELEASE_SKILL_ENTRY" prepare --root <path> --offline --json
|
|
59
67
|
# 生产 happy end:bound 基线必须 online;远端目标唯一性仍由 publish 全局预检
|
|
60
68
|
node "$RELEASE_SKILL_ENTRY" prepare --root <path> --online --production --json
|
|
@@ -66,8 +74,9 @@ node "$RELEASE_SKILL_ENTRY" prepare --root <path> --offline --acknowledge-gate-s
|
|
|
66
74
|
|
|
67
75
|
## 执行顺序
|
|
68
76
|
|
|
69
|
-
1. 校验配置 schema → 2.
|
|
70
|
-
|
|
77
|
+
1. 校验配置 schema → 2. 版本解析与发布文档新鲜度门(只读,RELEASE_DOCS_STALE)→
|
|
78
|
+
3. Hook 授权门 → 4. 运行 hooks 并复检文档新鲜度 → 5. 捕获 Git baseline →
|
|
79
|
+
6. 逐 unit 观察前序公开基线 → 7. 生成快照/扫描/README → 8. 原子写入 plan
|
|
71
80
|
|
|
72
81
|
## 故障路由
|
|
73
82
|
|
|
@@ -78,6 +87,8 @@ node "$RELEASE_SKILL_ENTRY" prepare --root <path> --offline --acknowledge-gate-s
|
|
|
78
87
|
| GATE_FAILED (bound + offline) | 改用 `--online --production`,不得把 unobserved-offline plan 交给 publish |
|
|
79
88
|
| GATE_FAILED (前序基线漂移) | 先取得并比较实际远端内容;人工选择 merge/adopt/reject。merge/adopt 都必须把接受内容落回 human-owned 权威源,并把 `previousPublicBaseline` 更新为接受状态的精确 repo/ref/commit 后重新 online production prepare;reject 停止调查,禁止改 `mode: none` 绕过 |
|
|
80
89
|
| GATE_FAILED (其他) | 修复门失败原因后重试;以 CLI exit code 为准 |
|
|
90
|
+
| RELEASE_DOCS_STALE | 文档相对说明源已陈旧;按详情运行只读演练,展示文件/语种/版本/摘要,经用户授权“本地发布文档写入”后执行写入,审阅提交再重新 prepare |
|
|
91
|
+
| RELEASE_DOCS_INVALID / TRANSLATION_MISSING / CONFLICT / REFRESH_STALE | 修复配置/说明源/目标或重新演练取得新 `refreshDigest`;不得扩大写入范围绕过 |
|
|
81
92
|
| SECRET_DETECTED | 移除密钥并更新 allowlist |
|
|
82
93
|
| CONFIG_INVALID | 检查 version.source 和 package.json |
|
|
83
94
|
|
|
@@ -24,7 +24,9 @@ description: 从已批准且摘要确认的生产计划发布冻结 Git branch/t
|
|
|
24
24
|
不得把沙箱通过描述成真实发布成功。
|
|
25
25
|
|
|
26
26
|
只发布 `prepare --production` 封存的 Git object 和 npm tarball,不从活动工作区重新
|
|
27
|
-
打包,不生成或覆盖 README
|
|
27
|
+
打包,不生成或覆盖 README,也永不隐式刷新工作树中的发布文档;
|
|
28
|
+
遇到 `RELEASE_DOCS_STALE` 或文档陈旧只能回到 `docs refresh` → 人工审阅 → 提交 →
|
|
29
|
+
重新 prepare。远端 branch/tag/Release/npm version 已存在、查询不确定、
|
|
28
30
|
认证失败或摘要漂移时,在全局预检阶段停止并交给人工。禁止覆盖、删除和自动回滚;
|
|
29
31
|
新建 ref 的 create-only CAS(`--force-with-lease=<ref>:`)只断言目标不存在,不授权覆盖。
|
|
30
32
|
|
|
@@ -26,7 +26,7 @@ marketplace 隔离消费者 checkpoint,但只恢复到 `PUBLISHED`;最终 np
|
|
|
26
26
|
|
|
27
27
|
## 职责与边界
|
|
28
28
|
|
|
29
|
-
查询远端实际状态,对照冻结计划识别一致/不一致检查点。已成功的步骤幂等跳过,只重试安全且未完成的步骤。远端冲突时停止并要求人工决策。不删除远端资源。`--run` 必需;重试需 `--approval`。
|
|
29
|
+
查询远端实际状态,对照冻结计划识别一致/不一致检查点。已成功的步骤幂等跳过,只重试安全且未完成的步骤。远端冲突时停止并要求人工决策。不删除远端资源。`--run` 必需;重试需 `--approval`。reconcile 永不隐式刷新工作树中的发布文档;陈旧文档只能回到 `docs refresh` → 人工审阅 → 提交 → 重新 prepare。
|
|
30
30
|
|
|
31
31
|
**阶段通过规则**: 本阶段的通过只能由 CLI exit code 0 和结构化状态码 `PUBLISHED` 确认。随后必须以 reconcile 返回的新 `runPath` 执行 verify;只有 verify 的 `VERIFIED` 才是完整终态。
|
|
32
32
|
|
|
@@ -4,10 +4,22 @@ import { basename, dirname, join, resolve } from 'node:path';
|
|
|
4
4
|
import { execFile as execFileCb } from 'node:child_process';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
6
|
import { parseNodeMajor, meetsMinimum, computeReadinessStatus } from '../src/core/node-version.mjs';
|
|
7
|
+
import { registerPathRedactor } from '../src/core/errors.mjs';
|
|
8
|
+
import { redactSensitivePaths } from '../src/core/redact.mjs';
|
|
9
|
+
|
|
10
|
+
// Install the path-redaction choke point eagerly and synchronously (static
|
|
11
|
+
// imports, no top-level await) so every ReleaseError constructed on any
|
|
12
|
+
// command path is redacted from the very first statement — in source mode and
|
|
13
|
+
// in the self-contained bundle alike. The bundle evaluates these static
|
|
14
|
+
// imports during its top level, before any lazy command initialization or
|
|
15
|
+
// handler runs; keeping this module graph free of top-level await is also
|
|
16
|
+
// what lets the bundled artifacts tree settle (AC-7: the launcher must never
|
|
17
|
+
// exit 13 "Detected unsettled top-level await" for a command it owns).
|
|
18
|
+
registerPathRedactor(redactSensitivePaths);
|
|
7
19
|
|
|
8
20
|
const execFile = promisify(execFileCb);
|
|
9
21
|
|
|
10
|
-
const COMMANDS = new Set(['help', 'setup', 'assess', 'prepare', 'approve', 'publish', 'reconcile', 'verify', 'artifacts']);
|
|
22
|
+
const COMMANDS = new Set(['help', 'setup', 'assess', 'prepare', 'approve', 'publish', 'reconcile', 'verify', 'artifacts', 'docs']);
|
|
11
23
|
|
|
12
24
|
/**
|
|
13
25
|
* Check if a command is available and get its version.
|
|
@@ -128,6 +140,11 @@ function getCapabilityMaturity() {
|
|
|
128
140
|
mode: 'offline local writes',
|
|
129
141
|
description: 'Freeze a release plan with snapshots and gates',
|
|
130
142
|
},
|
|
143
|
+
docs: {
|
|
144
|
+
available: true,
|
|
145
|
+
mode: 'read-only dry-run / explicit local document write',
|
|
146
|
+
description: 'Refresh declared README managed regions and CHANGELOG current-version entries from one structured notes source; write requires --write, exact --confirm-refresh, and --ack-local-document-write; never commits, pushes, or publishes',
|
|
147
|
+
},
|
|
131
148
|
publish: {
|
|
132
149
|
available: true,
|
|
133
150
|
mode: 'controlled production (protocol-tested; no OS/network sandbox)',
|
|
@@ -162,6 +179,7 @@ Commands:
|
|
|
162
179
|
reconcile Resume PARTIAL state from evidence; conflicts require a human
|
|
163
180
|
verify Fresh remote and consumer verification; only this reaches VERIFIED
|
|
164
181
|
artifacts Artifact status, inspect, update/apply, resolution, and diagnostics
|
|
182
|
+
docs Refresh declared release documents (read-only dry-run by default)
|
|
165
183
|
|
|
166
184
|
Options:
|
|
167
185
|
--root <path> Project root directory (default: cwd)
|
|
@@ -175,6 +193,9 @@ Options:
|
|
|
175
193
|
--answers <path> Human-reviewed setup answers JSON
|
|
176
194
|
--write Create an absent project.yaml during setup; never overwrites
|
|
177
195
|
--confirm-setup <digest> Confirm exact setup facts and answers before create
|
|
196
|
+
--unit <id> Release unit whose declared release documents are refreshed (docs refresh)
|
|
197
|
+
--confirm-refresh <sha256:...> Confirm the exact dry-run refreshDigest before any document write
|
|
198
|
+
--ack-local-document-write Acknowledge the explicit local release-document write (docs refresh --write)
|
|
178
199
|
--acknowledge-hook-side-effects Acknowledge unsandboxed legacy hook execution
|
|
179
200
|
--acknowledge-gate-side-effects Acknowledge unsandboxed local verification gate execution
|
|
180
201
|
--json Output results as JSON
|
|
@@ -189,6 +210,7 @@ Safety:
|
|
|
189
210
|
- prepare output goes to .release-skill/ directory only
|
|
190
211
|
- User-configured hooks may write anywhere and perform remote operations
|
|
191
212
|
- To ensure zero remote writes, disable hooks or audit them separately
|
|
213
|
+
- docs refresh --write rewrites only declared README managed regions and the current CHANGELOG entry after exact refreshDigest confirmation; it never commits, pushes, tags, publishes, or installs.
|
|
192
214
|
- publish requires explicit approval and an exact plan-digest confirmation
|
|
193
215
|
- publish consumes frozen Git/npm artifacts, never the live workspace
|
|
194
216
|
- existing remote objects and uncertain checks stop for human intervention
|
|
@@ -206,9 +228,25 @@ const positional = args.filter(a => !a.startsWith('--'));
|
|
|
206
228
|
const command = positional[0];
|
|
207
229
|
|
|
208
230
|
if (!command && (args.includes('--version') || args.includes('-v'))) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
231
|
+
// The version probe must be install-closure independent: the npm closure
|
|
232
|
+
// resolves ../package.json from bin/, but the Claude and Codex adapter
|
|
233
|
+
// closures ship the bundle at a different depth with no package.json at
|
|
234
|
+
// all. Bundled closures therefore carry the package identity as a
|
|
235
|
+
// build-time constant (__bundlePkg) injected by the esbuild banner in
|
|
236
|
+
// scripts/build-bundle.mjs; only source mode reads the file.
|
|
237
|
+
let pkg;
|
|
238
|
+
if (typeof __bundlePkg !== 'undefined') {
|
|
239
|
+
pkg = __bundlePkg;
|
|
240
|
+
} else {
|
|
241
|
+
// Source mode only (the bundle always carries __bundlePkg). Deliberately
|
|
242
|
+
// not a require('../package.json'): esbuild would inline it into the
|
|
243
|
+
// bundle as the one remaining bundle-relative file dependency, which is
|
|
244
|
+
// exactly what breaks the adapter closures.
|
|
245
|
+
const { readFileSync } = await import('node:fs');
|
|
246
|
+
const { fileURLToPath } = await import('node:url');
|
|
247
|
+
const pkgPath = join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json');
|
|
248
|
+
pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
249
|
+
}
|
|
212
250
|
if (hasJson) {
|
|
213
251
|
console.log(JSON.stringify({
|
|
214
252
|
command: 'version',
|
|
@@ -269,6 +307,7 @@ if (!command || command === 'help') {
|
|
|
269
307
|
setup: 'read-only by default; create-once requires answers plus exact setupDigest confirmation',
|
|
270
308
|
assess: 'read-only (default); --output writes local report',
|
|
271
309
|
prepare: 'offline local writes; configured hooks/gates require their explicit side-effect acknowledgements',
|
|
310
|
+
docs: 'read-only dry-run by default; write requires --write, exact --confirm-refresh, and --ack-local-document-write; never commits, pushes, or publishes',
|
|
272
311
|
onlinePrepare: 'previous-public-baseline observation available; production mode freezes publish artifacts and fails closed on drift or unknown state',
|
|
273
312
|
publish: 'GitHub/npm plus configured Claude/Codex consumer checkpoints are protocol-tested without an OS/network sandbox; approval and exact digest confirmation required',
|
|
274
313
|
reconcile: 'PARTIAL recovery is protocol-tested without an OS/network sandbox; remote conflicts require human intervention',
|
|
@@ -802,6 +841,126 @@ if (command === 'artifacts') {
|
|
|
802
841
|
}
|
|
803
842
|
}
|
|
804
843
|
|
|
844
|
+
// --- Docs command routing ---
|
|
845
|
+
if (command === 'docs') {
|
|
846
|
+
try {
|
|
847
|
+
const { ReleaseError, MISSING_PARAMETERS } = await import('../src/core/errors.mjs');
|
|
848
|
+
|
|
849
|
+
// --root is extracted and validated here (the router resolves the project
|
|
850
|
+
// root): a following flag is never accepted as the path, and a duplicated
|
|
851
|
+
// --root is an explicit parameter error (previously silently ignored).
|
|
852
|
+
const rootIndexes = [];
|
|
853
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
854
|
+
if (args[i] === '--root') rootIndexes.push(i);
|
|
855
|
+
}
|
|
856
|
+
if (rootIndexes.length > 1) {
|
|
857
|
+
throw new ReleaseError(
|
|
858
|
+
MISSING_PARAMETERS,
|
|
859
|
+
'docs received --root more than once',
|
|
860
|
+
{ reason: 'DUPLICATE_PARAMETER', field: '--root' },
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
let rawRoot = process.cwd();
|
|
864
|
+
if (rootIndexes.length === 1) {
|
|
865
|
+
rawRoot = args[rootIndexes[0] + 1];
|
|
866
|
+
if (typeof rawRoot !== 'string' || rawRoot.length === 0 || rawRoot.startsWith('-')) {
|
|
867
|
+
throw new ReleaseError(
|
|
868
|
+
MISSING_PARAMETERS,
|
|
869
|
+
'docs --root requires a path value',
|
|
870
|
+
{ reason: 'MISSING_VALUE', field: '--root' },
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
const root = resolve(rawRoot);
|
|
875
|
+
|
|
876
|
+
// The docs subcommand is the first bare positional token after the `docs`
|
|
877
|
+
// command token itself. Valued flags and their values are skipped so
|
|
878
|
+
// `--root <path>` can never be mistaken for the subcommand; any flag
|
|
879
|
+
// outside the recognized docs set (including unregistered valued flags)
|
|
880
|
+
// is rejected here so its value can never be mistaken for the subcommand.
|
|
881
|
+
const valuedDocsFlags = new Set(['--root', '--unit', '--confirm-refresh']);
|
|
882
|
+
const booleanDocsFlags = new Set(['--json', '--write', '--ack-local-document-write']);
|
|
883
|
+
let docsSubcommand;
|
|
884
|
+
let sawCommandToken = false;
|
|
885
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
886
|
+
const token = args[i];
|
|
887
|
+
if (typeof token !== 'string') continue;
|
|
888
|
+
if (token.startsWith('--')) {
|
|
889
|
+
const eq = token.indexOf('=');
|
|
890
|
+
const flag = eq === -1 ? token : token.slice(0, eq);
|
|
891
|
+
if (valuedDocsFlags.has(flag)) {
|
|
892
|
+
if (eq === -1) i += 1; // space-separated form: skip the value too
|
|
893
|
+
continue;
|
|
894
|
+
}
|
|
895
|
+
if (booleanDocsFlags.has(flag)) continue;
|
|
896
|
+
throw new ReleaseError(
|
|
897
|
+
MISSING_PARAMETERS,
|
|
898
|
+
`docs does not accept ${flag}`,
|
|
899
|
+
{ reason: 'UNRECOGNIZED_PARAMETER', parameter: flag },
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
if (token.startsWith('-') && token.length > 1) {
|
|
903
|
+
throw new ReleaseError(
|
|
904
|
+
MISSING_PARAMETERS,
|
|
905
|
+
`docs does not accept ${token}`,
|
|
906
|
+
{ reason: 'UNRECOGNIZED_PARAMETER', parameter: token },
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
if (!sawCommandToken) {
|
|
910
|
+
sawCommandToken = true; // the `docs` command token itself
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
913
|
+
docsSubcommand = token;
|
|
914
|
+
break;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
const { runDocsCommand } = await import('../src/commands/docs.mjs');
|
|
918
|
+
const result = await runDocsCommand({ subcommand: docsSubcommand, args, root });
|
|
919
|
+
|
|
920
|
+
if (hasJson) {
|
|
921
|
+
console.log(JSON.stringify(result, null, 2));
|
|
922
|
+
} else if (result.mode === 'dry-run') {
|
|
923
|
+
console.log(`Status: ${result.status}`);
|
|
924
|
+
console.log(`Unit: ${result.unitId}`);
|
|
925
|
+
console.log(`Version: ${result.version}`);
|
|
926
|
+
console.log(`Refresh digest: ${result.refreshDigest}`);
|
|
927
|
+
for (const file of result.files) {
|
|
928
|
+
const marker = file.changed ? '' : ' (unchanged)';
|
|
929
|
+
console.log(` ${file.path} ${file.kind} ${file.locale} ${file.change}${marker}`);
|
|
930
|
+
}
|
|
931
|
+
if (result.nextCommand?.argv) {
|
|
932
|
+
console.log(`Next: ${result.nextCommand.argv.join(' ')}`);
|
|
933
|
+
}
|
|
934
|
+
if (result.nextCommand?.writeArgv) {
|
|
935
|
+
console.log(`Next (write): ${result.nextCommand.writeArgv.join(' ')}`);
|
|
936
|
+
}
|
|
937
|
+
} else {
|
|
938
|
+
console.log(`Status: ${result.status}`);
|
|
939
|
+
console.log(`Unit: ${result.unitId}`);
|
|
940
|
+
console.log(`Version: ${result.version}`);
|
|
941
|
+
console.log(`Refresh digest: ${result.refreshDigest}`);
|
|
942
|
+
if (result.refreshed) {
|
|
943
|
+
console.log(`Transaction: ${result.transactionId}`);
|
|
944
|
+
for (const path of result.refreshedPaths ?? []) {
|
|
945
|
+
console.log(` refreshed ${path}`);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
process.exit(0);
|
|
951
|
+
} catch (err) {
|
|
952
|
+
// docs parameter errors must surface the stable JSON error shape even in
|
|
953
|
+
// text mode (CLI parameter validation precedes any service I/O).
|
|
954
|
+
console.log(JSON.stringify({
|
|
955
|
+
error: err.code ?? 'UNKNOWN_ERROR',
|
|
956
|
+
message: err.message,
|
|
957
|
+
details: err.details ?? {},
|
|
958
|
+
exitCode: err.exitCode ?? 1,
|
|
959
|
+
}));
|
|
960
|
+
process.exit(err.exitCode ?? 1);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
805
964
|
// Placeholder: remaining commands will be wired in later tasks
|
|
806
965
|
console.error(`Command '${command}' is not yet implemented.`);
|
|
807
966
|
process.exit(1);
|