@geoly-ai/social-hub-cli 0.3.19 → 0.3.21

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.
Files changed (50) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/accounts-list-offset.test.d.ts +2 -0
  3. package/dist/accounts-list-offset.test.d.ts.map +1 -0
  4. package/dist/accounts-list-offset.test.js +53 -0
  5. package/dist/accounts-list-offset.test.js.map +1 -0
  6. package/dist/cmd-manifest.json +4 -2
  7. package/dist/compliance-risk-list-offset.test.d.ts +2 -0
  8. package/dist/compliance-risk-list-offset.test.d.ts.map +1 -0
  9. package/dist/compliance-risk-list-offset.test.js +63 -0
  10. package/dist/compliance-risk-list-offset.test.js.map +1 -0
  11. package/dist/content-review.test.js +1 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +40 -4
  14. package/dist/index.js.map +1 -1
  15. package/dist/index.test.js +16 -0
  16. package/dist/index.test.js.map +1 -1
  17. package/dist/register-calendar-directives.d.ts +64 -0
  18. package/dist/register-calendar-directives.d.ts.map +1 -0
  19. package/dist/register-calendar-directives.js +135 -0
  20. package/dist/register-calendar-directives.js.map +1 -0
  21. package/dist/register-calendar-directives.test.d.ts +2 -0
  22. package/dist/register-calendar-directives.test.d.ts.map +1 -0
  23. package/dist/register-calendar-directives.test.js +127 -0
  24. package/dist/register-calendar-directives.test.js.map +1 -0
  25. package/dist/register-shared.d.ts.map +1 -1
  26. package/dist/register-shared.js +13 -2
  27. package/dist/register-shared.js.map +1 -1
  28. package/dist/register-shared.test.js +78 -0
  29. package/dist/register-shared.test.js.map +1 -1
  30. package/package.json +2 -2
  31. package/skills/README.md +23 -22
  32. package/skills/manifest.json +6 -1
  33. package/skills/reddit-content-rewrite/SKILL.md +29 -0
  34. package/skills/reddit-content-writing/SKILL.md +53 -4
  35. package/skills/reddit-delivery-check/SKILL.md +38 -6
  36. package/skills/reddit-matrix.lock.json +6 -6
  37. package/skills/reddit-phase-brief/SKILL.md +39 -4
  38. package/skills/reddit-phase-brief/references/phase4-brief-workflow.md +43 -3
  39. package/skills/reddit-strategy-shared/references/content-diversity-contract.md +7 -2
  40. package/skills/reddit-strategy-shared/references/content-review-gate.md +48 -1
  41. package/skills/reddit-strategy-shared/references/handoff-schemas.md +72 -2
  42. package/skills/reddit-strategy-shared/references/native-copy-profile.md +207 -0
  43. package/skills/reddit-strategy-shared/references/persona-card-contract.md +210 -0
  44. package/skills/reddit-strategy-shared/references/version-manifest.md +60 -0
  45. package/skills/reddit-subreddit-compliance/SKILL.md +28 -1
  46. package/skills/social-hub-calendar-directives/SKILL.md +548 -0
  47. package/skills/social-hub-calendar-jobs/SKILL.md +10 -0
  48. package/skills/social-hub-cli/SKILL.md +2 -0
  49. package/skills/social-hub-notifications/SKILL.md +7 -0
  50. package/skills/social-hub-publishing/SKILL.md +5 -0
@@ -1,3 +1,6 @@
1
+ import { mkdtempSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
1
4
  import { Command } from "commander";
2
5
  import { afterEach, describe, expect, it, vi } from "vitest";
3
6
  // getLatestVersion is the only network call; pin it equal to the CLI version so
@@ -15,6 +18,36 @@ vi.mock("./skill-update-check.js", async (importOriginal) => {
15
18
  const actual = await importOriginal();
16
19
  return { ...actual, syncManagedSkillsNow: syncSpy };
17
20
  });
21
+ // doctor 会打网络 + 读凭证:健康检查与 auth context 全部打桩,凭证走 env。
22
+ vi.mock("@geoly-ai/social-hub-sdk", async (importOriginal) => {
23
+ const actual = await importOriginal();
24
+ return {
25
+ ...actual,
26
+ getSocialHubHealth: vi.fn(async () => ({
27
+ ok: true,
28
+ db: "up",
29
+ redis: "up",
30
+ })),
31
+ getSocialHubHealthLive: vi.fn(async () => ({ ok: true })),
32
+ };
33
+ });
34
+ vi.mock("./client.js", async (importOriginal) => {
35
+ const actual = await importOriginal();
36
+ return {
37
+ ...actual,
38
+ getBaseUrl: () => "https://example.invalid/api",
39
+ requireClient: () => ({
40
+ getAuthContext: async () => ({ role: "admin", teamId: TEST_TEAM_ID }),
41
+ }),
42
+ };
43
+ });
44
+ /** 可辨识的假凭证:出现在任何输出里都只可能来自泄漏。 */
45
+ const LEAK_CANARY_API_KEY = "soh_cli_LEAKCANARY_do_not_print_0123456789";
46
+ const TEST_TEAM_ID = "00000000-0000-4000-8000-000000000001";
47
+ process.env.SOCIAL_HUB_CONFIG_DIR = mkdtempSync(join(tmpdir(), "social-hub-cli-test-"));
48
+ process.env.SOCIAL_HUB_API_KEY = LEAK_CANARY_API_KEY;
49
+ process.env.SOCIAL_HUB_API_URL = "https://example.invalid/api";
50
+ process.env.SOCIAL_HUB_TEAM_ID = TEST_TEAM_ID;
18
51
  const { registerDoctorAndVersion } = await import("./register-shared.js");
19
52
  function run(argv) {
20
53
  const program = new Command();
@@ -38,4 +71,49 @@ describe("self-update (already latest)", () => {
38
71
  log.mockRestore();
39
72
  });
40
73
  });
74
+ describe("doctor --json 不得泄漏 apiKey", () => {
75
+ it("完整输出里不出现凭证明文", async () => {
76
+ const lines = [];
77
+ const log = vi
78
+ .spyOn(console, "log")
79
+ .mockImplementation((...args) => {
80
+ lines.push(args.map((a) => String(a)).join(" "));
81
+ });
82
+ const errLines = [];
83
+ const stderr = vi
84
+ .spyOn(process.stderr, "write")
85
+ .mockImplementation((chunk) => {
86
+ errLines.push(String(chunk));
87
+ return true;
88
+ });
89
+ const exit = vi
90
+ .spyOn(process, "exit")
91
+ .mockImplementation((() => undefined));
92
+ let stdout = "";
93
+ let stderrText = "";
94
+ try {
95
+ await run(["doctor", "--json"]);
96
+ stdout = lines.join("\n");
97
+ stderrText = errLines.join("\n");
98
+ }
99
+ finally {
100
+ log.mockRestore();
101
+ stderr.mockRestore();
102
+ }
103
+ // 断言整段序列化文本,而不是某个字段——换个字段泄漏也要被抓住。
104
+ expect(stdout).not.toContain(LEAK_CANARY_API_KEY);
105
+ expect(stderrText).not.toContain(LEAK_CANARY_API_KEY);
106
+ // 输出确实产生了、且确实读到了这把 key(否则上面的断言是空过的)。
107
+ const payload = JSON.parse(stdout);
108
+ expect(payload.checks.apiKey).toBe("set");
109
+ expect(payload.checks.resolvedConfig.sources.apiKey).toBe("env");
110
+ expect(typeof payload.checks.configPath).toBe("string");
111
+ expect(payload.checks.configPath).toContain("config.json");
112
+ // 脱敏形状确实是脱敏的(既非明文,也非整把 key 的子串)。
113
+ expect(payload.checks.resolvedConfig.apiKey).not.toBe(LEAK_CANARY_API_KEY);
114
+ expect(LEAK_CANARY_API_KEY).not.toContain(payload.checks.resolvedConfig.apiKey);
115
+ expect(exit).not.toHaveBeenCalled();
116
+ exit.mockRestore();
117
+ });
118
+ });
41
119
  //# sourceMappingURL=register-shared.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"register-shared.test.js","sourceRoot":"","sources":["../src/register-shared.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE7D,gFAAgF;AAChF,4EAA4E;AAC5E,EAAE,CAAC,IAAI,CAAC,mBAAmB,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE;IACpD,MAAM,MAAM,GAAG,MAAM,cAAc,EAAsC,CAAC;IAC1E,OAAO,EAAE,GAAG,MAAM,EAAE,gBAAgB,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;AACrE,CAAC,CAAC,CAAC;AACH,EAAE,CAAC,IAAI,CAAC,mBAAmB,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE;IACpD,MAAM,MAAM,GAAG,MAAM,cAAc,EAAsC,CAAC;IAC1E,OAAO,EAAE,GAAG,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC;AACrD,CAAC,CAAC,CAAC;AACH,MAAM,OAAO,GAAG,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;AACvE,EAAE,CAAC,IAAI,CAAC,yBAAyB,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE;IAC1D,MAAM,MAAM,GACV,MAAM,cAAc,EAA4C,CAAC;IACnE,OAAO,EAAE,GAAG,MAAM,EAAE,oBAAoB,EAAE,OAAO,EAAE,CAAC;AACtD,CAAC,CAAC,CAAC;AAEH,MAAM,EAAE,wBAAwB,EAAE,GAAG,MAAM,MAAM,CAAC,sBAAsB,CAAC,CAAC;AAE1E,SAAS,GAAG,CAAC,IAAc;IACzB,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;IAC9B,wBAAwB,CAAC,OAAO,CAAC,CAAC;IAClC,OAAO,OAAO,CAAC,UAAU,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;AACtD,CAAC;AAED,SAAS,CAAC,GAAG,EAAE;IACb,OAAO,CAAC,SAAS,EAAE,CAAC;AACtB,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,8BAA8B,EAAE,GAAG,EAAE;IAC5C,EAAE,CAAC,0CAA0C,EAAE,KAAK,IAAI,EAAE;QACxD,MAAM,GAAG,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAClE,MAAM,GAAG,CAAC,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC,CAAC;QACxC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QACvC,GAAG,CAAC,WAAW,EAAE,CAAC;IACpB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,KAAK,IAAI,EAAE;QACtE,MAAM,GAAG,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAClE,MAAM,GAAG,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC;QAC3B,MAAM,CAAC,OAAO,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACzC,GAAG,CAAC,WAAW,EAAE,CAAC;IACpB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"register-shared.test.js","sourceRoot":"","sources":["../src/register-shared.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geoly-ai/social-hub-cli",
3
- "version": "0.3.19",
3
+ "version": "0.3.21",
4
4
  "type": "module",
5
5
  "description": "social-hub CLI for Social Ops Hub",
6
6
  "repository": {
@@ -23,7 +23,7 @@
23
23
  "dependencies": {
24
24
  "commander": "^12.1.0",
25
25
  "@geoly-ai/social-hub-authz": "0.0.17",
26
- "@geoly-ai/social-hub-sdk": "0.0.56"
26
+ "@geoly-ai/social-hub-sdk": "0.0.58"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/node": "^22.10.2",
package/skills/README.md CHANGED
@@ -119,28 +119,29 @@ pnpm skills:check
119
119
 
120
120
  ## Skill 列表
121
121
 
122
- | Skill | 用途 |
123
- | ------------------------------------------------------------------ | ---------------------------------- |
124
- | [social-hub-cli](social-hub-cli) | 总路由 + 领域 skill 索引 |
125
- | [social-hub-shared](social-hub-shared) | config、auth、doctor、context |
126
- | [social-hub-ops-runtime](social-hub-ops-runtime) | claim / complete / fail |
127
- | [social-hub-openclaw-context](social-hub-openclaw-context) | DB 聚合上下文 |
128
- | [social-hub-accounts](social-hub-accounts) | 账号 CRUD、browser-envs、personas |
129
- | [social-hub-posts](social-hub-posts) | Reddit 帖快照 CRUD、batch-upsert |
130
- | [social-hub-style-curator](social-hub-style-curator) | style-marks、curator、style-guide |
131
- | [social-hub-style-profiles](social-hub-style-profiles) | 板块画像四眼、帖子标注 |
132
- | [social-hub-calendar-jobs](social-hub-calendar-jobs) | 日历与任务 |
133
- | [social-hub-publishing](social-hub-publishing) | 发布闭环 |
134
- | [social-hub-content-review](social-hub-content-review) | 评论发布前复审/优化(gate 层③) |
135
- | [social-hub-post-review](social-hub-post-review) | 帖子发布前审核/优化(post-review) |
136
- | [social-hub-events-observability](social-hub-events-observability) | events、dashboard、audit、reports |
137
- | [social-hub-graph-compliance](social-hub-graph-compliance) | 图谱与合规风控 |
138
- | [social-hub-admin](social-hub-admin) | 系统/团队管理、通知渠道 |
139
- | [social-hub-intelligence](social-hub-intelligence) | 板块情报与 insights |
140
- | [social-hub-subreddit-pools](social-hub-subreddit-pools) | 运营板块池 catalog/aliases/tier |
141
- | [social-hub-scrape](social-hub-scrape) | 通用网页抓取(Firecrawl) |
142
- | [social-hub-migration](social-hub-migration) | 导入导出与 replay |
143
- | [social-hub-notifications](social-hub-notifications) | 事件订阅通知:notify pull/ack/订阅 |
122
+ | Skill | 用途 |
123
+ | ------------------------------------------------------------------ | -------------------------------------------- |
124
+ | [social-hub-cli](social-hub-cli) | 总路由 + 领域 skill 索引 |
125
+ | [social-hub-shared](social-hub-shared) | config、auth、doctor、context |
126
+ | [social-hub-ops-runtime](social-hub-ops-runtime) | claim / complete / fail |
127
+ | [social-hub-openclaw-context](social-hub-openclaw-context) | DB 聚合上下文 |
128
+ | [social-hub-accounts](social-hub-accounts) | 账号 CRUD、browser-envs、personas |
129
+ | [social-hub-posts](social-hub-posts) | Reddit 帖快照 CRUD、batch-upsert |
130
+ | [social-hub-style-curator](social-hub-style-curator) | style-marks、curator、style-guide |
131
+ | [social-hub-style-profiles](social-hub-style-profiles) | 板块画像四眼、帖子标注 |
132
+ | [social-hub-calendar-jobs](social-hub-calendar-jobs) | 日历与任务 |
133
+ | [social-hub-publishing](social-hub-publishing) | 发布闭环 |
134
+ | [social-hub-content-review](social-hub-content-review) | 评论发布前复审/优化(gate 层③) |
135
+ | [social-hub-post-review](social-hub-post-review) | 帖子发布前审核/优化(post-review) |
136
+ | [social-hub-events-observability](social-hub-events-observability) | events、dashboard、audit、reports |
137
+ | [social-hub-graph-compliance](social-hub-graph-compliance) | 图谱与合规风控 |
138
+ | [social-hub-admin](social-hub-admin) | 系统/团队管理、通知渠道 |
139
+ | [social-hub-intelligence](social-hub-intelligence) | 板块情报与 insights |
140
+ | [social-hub-subreddit-pools](social-hub-subreddit-pools) | 运营板块池 catalog/aliases/tier |
141
+ | [social-hub-scrape](social-hub-scrape) | 通用网页抓取(Firecrawl) |
142
+ | [social-hub-migration](social-hub-migration) | 导入导出与 replay |
143
+ | [social-hub-notifications](social-hub-notifications) | 事件订阅通知:notify pull/ack/订阅 |
144
+ | [social-hub-calendar-directives](social-hub-calendar-directives) | 发布日历指令:外部 agent 建 cron + preflight |
144
145
 
145
146
  ### Reddit 矩阵 Skill(随 CLI 一同发布)
146
147
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 1,
3
3
  "package": "social-ops-hub",
4
- "cliVersion": "0.3.19",
4
+ "cliVersion": "0.3.21",
5
5
  "repository": "social-ops-hub",
6
6
  "publish": {
7
7
  "registry": "https://skill.sh",
@@ -109,6 +109,11 @@
109
109
  "path": "social-hub-notifications",
110
110
  "description": "事件订阅通知:notify subscriptions/pull/ack/source-configs,本机 agent 拉走后自行执行副作用"
111
111
  },
112
+ {
113
+ "id": "social-hub-calendar-directives",
114
+ "path": "social-hub-calendar-directives",
115
+ "description": "发布日历指令:外部 agent 收 upsert/cancel 建 UTC 一次性 cron,到点先过 calendar preflight"
116
+ },
112
117
  {
113
118
  "id": "reddit-strategy-orchestrator",
114
119
  "path": "reddit-strategy-orchestrator",
@@ -36,6 +36,16 @@ description: >-
36
36
  增加限制说明、增加社区观察句、把分节式结构改成叙事弧光、移动品牌出现位置、
37
37
  更换 `StructureFamily` 或 `ContentFunction`、按固定比例削减产品内容。
38
38
 
39
+ **客户的收紧标准不进默认层。** 品牌出现次数上限、必须以问句结尾、PDP 语言限制、固定
40
+ 输出段式这类要求属于可选 profile(`AcceptanceProfile: NativeCopy`,见
41
+ `../reddit-strategy-shared/references/native-copy-profile.md`)。**这些收紧项分属两个上游
42
+ 阶段,本 skill 两类都不施加**:结构类(品牌次数上限、受限 `EndingMode` 集合)只能由
43
+ `reddit-phase-brief` 在**分配时**施加——`EndingMode` / `BrandEntryMode` 都是锁定指纹字段,
44
+ 改写阶段动它们等于绕过结构预分配并作废碰撞检查;register 类(PDP 语言、fake-Reddit-English、
45
+ 禁用官方全名)由撰写 skill 在**出初稿时**遵守,本 skill 不得新加或加码——那是把「只让你修」
46
+ 的稿子做成 house voice 归一化。把任何收紧项写进默认层,就等于给所有品牌强加一套
47
+ house voice —— 那正是本 skill 被重新定义为约束保持型修复器要防的事。
48
+
39
49
  **没有该 subreddit 的 baseline(`SampleSetID`)时,不得声称已完成 subreddit-native 改写。**
40
50
  此时仍可工作,但只能声称到对应等级(见下方 `SubredditAdaptationStatus` 三档),
41
51
  并把缺口写进 `BlockingGaps`。
@@ -269,6 +279,10 @@ ExplicitStructureSpec:
269
279
  条件输入(可为 NotProvided;仅当本次修复确实依赖它时才 BlockingGap):
270
280
  - TargetSubreddit / SampleSetID / AnchorSlot / ContentKey / MappingRuleID
271
281
  - OpeningMove / EvidenceShape / BrandEntryMode / CaveatMode / EndingMode / LengthBand
282
+ - PersonaCardRef / PersonaCardVersion / PersonaCardHash:Brief 分配的知识边界
283
+ (含 `permittedExperienceClaims` / `knows` / `doesNotKnow` /
284
+ `brandRelationshipDisclosed`;契约见
285
+ `../reddit-strategy-shared/references/persona-card-contract.md`)
272
286
  - KnownProductFacts:可追溯的产品事实(SKU 基准表 / VOC / 甲方语料)
273
287
  - RealLimitations:真实存在的限制
274
288
  - RequiredDisclosure:必须保留的披露
@@ -482,6 +496,16 @@ ReadyForUse: No
482
496
  - 不编造作者身份、国籍、语言背景
483
497
  - **不主动制造 typo、拼写失误、错误冠词、介词误用、缺主语、"移民语法"**——
484
498
  这些不只是同质化,是伪造
499
+ - **不得新增、也不得保留与已分配 Persona Card 相矛盾的主张。** 稿件声称了
500
+ `permittedExperienceClaims` 未授权的第一手经历(ownership / delivery / wear /
501
+ service),或表现出 `doesNotKnow` 列出的知识,属于**无来源主张**,落在
502
+ `FactualExpression` 的既有职责内:按最小改动去掉该主张即可。
503
+ ⚠️ 但**加宽边界不在允许范围内**:本 skill 不得撰写、递增、改写任何 Card。需要更宽的
504
+ 边界就输出 `PersonaCardConflict: Yes` + 相矛盾的原句与对应 Card 字段,退回
505
+ `reddit-phase-brief` 换新版本 Card。`PersonaCardRef` / `PersonaCardVersion` /
506
+ `PersonaCardHash` 一律原样保持。
507
+ ⚠️ 命中「第一手经历」措辞本身**不是违规**——`mine arrived` 在授权 `delivery` 时完全正常;
508
+ 只有与 Card 矛盾才算。也**不得**因此声称做过任何知识边界审核:这只是本地修复。
485
509
  - 所有进入可发布文本(标题 / 正文 / 评论 / 回复)的实质性 claim 必须是 `ClaimEvidenceStatus: Cited`(可映射到已批准证据 `KnownProductFacts` / `SourceEvidence`)或 `ExternallyVerified`(非产品事实类、有外部权威来源 `ExternalSourceRef`);`Inferred`(无来源的模型推断)禁入可发布文本——删除或退回补证据,无法删且必需则 BlockingGap。三态权威定义见 `../reddit-strategy-shared/references/evidence-policy.md`
486
510
 
487
511
  ### 绝对禁用营销词
@@ -825,6 +849,11 @@ subreddit 规则、属于 `SampleSetID` 支持的异常值,否则保持原文
825
849
  - FingerprintSyncStatus: PendingWritingOwner | Confirmed | NotApplicable
826
850
  - DraftHash:
827
851
 
852
+ - PersonaCardRef: # 原样保持;本 skill 不得撰写或改写 Card
853
+ - PersonaCardVersion:
854
+ - PersonaCardHash:
855
+ - PersonaCardConflict: Yes | No | NotApplicable # Yes ⇒ 退回 reddit-phase-brief 换新版本 Card
856
+
828
857
  - TargetSubreddit:
829
858
  - SubredditAdaptationStatus: Verified | RulesOnly | NotClaimed # 阻断统一写 RoutingStatus / BlockingGaps
830
859
  - FactsAdded: None / Listed
@@ -37,7 +37,9 @@ This skill writes planned Reddit posts and comments. It does not redo strategy,
37
37
  - `references/pgc-writing-strategy.md` when writing PGC posts
38
38
  - `references/comp-mention-strategy.md` only when competitor mention permission is explicitly `Yes`
39
39
  - installed `reddit-brand-risk-response` output for pre-publish bot/shill/promo/claim risk gates when the batch is not explicitly low risk
40
- - `../reddit-strategy-shared/references/content-review-gate.md` for the mandatory Social Hub review loop
40
+ - `../reddit-strategy-shared/references/content-review-gate.md` for the mandatory Social Hub review loop, including how to read a dimension the Hub result reports as `not_assessed`
41
+ - `../reddit-strategy-shared/references/persona-card-contract.md` for the Brief-assigned Persona Card this skill writes **inside** and never authors
42
+ - `../reddit-strategy-shared/references/native-copy-profile.md` only when the batch runs `AcceptanceProfile: NativeCopy`
41
43
 
42
44
  ## Gate
43
45
 
@@ -49,6 +51,7 @@ Additionally, stop before writing a single word of body copy if any of these hol
49
51
  - a main-post row has no `StructureFamily` assigned by the Brief
50
52
  - a main-post row has no `AssignedFlair`, or the baseline card's `FlairCoverageStatus` is `Insufficient` → `BlockingGap`, return to `reddit-phase-brief`
51
53
  - a main-post row has no `PostingTrigger` assigned by the Brief, or its `Planned` `PostingTrigger` already violates the `PostingTrigger × BrandEntryMode` cross-constraint → `BlockingGap` (the post-draft re-derive of `PostingTrigger` vs `Planned` is checked after drafting, in the Writing Rules, not here)
54
+ - a main-post row has no `PersonaCardRef` assigned by the Brief → `BlockingGap`, return to `reddit-phase-brief`. **Never author a Card here**: a writer that supplies its own knowledge boundary grants itself exactly the boundary the draft needs, which is the bypass pre-assignment exists to close
52
55
  - a target subreddit has no baseline card, or it is `Expired` / `Incomplete` → `BlockingGap: BaselineMissingOrStale`
53
56
  - `memory/phase4-叙事指纹索引.md` has not been read, or the rolling-window collision check has not been run
54
57
  - the collision check fails and no sample-based exception is on record → `BlockingGap: StructureCollision`, return to `reddit-phase-brief`
@@ -78,6 +81,17 @@ C-B/C-NB, and rows that were previously low risk.
78
81
  persona or inline style guide. Resolve an approved `brandContext` when Hub
79
82
  requires an explicit brand relationship; do not guess a brand or send an
80
83
  arbitrary `stage` value.
84
+ **Never add the Persona Card, any Card field, or a Card-derived score to the
85
+ request, and never pack it into `subredditRules` or the thread context to get
86
+ it through.** The request schemas are strict, so an extra field is a rejected
87
+ call rather than an ignored key, and a free-text slot reserved for rule
88
+ evidence is not a transport for planner text. The Card governs writing only
89
+ (`../reddit-strategy-shared/references/persona-card-contract.md` §7).
90
+ Recording which quadrant the batch was written for is a **local** planner
91
+ declaration (`LocalReviewIntent`); the review call carries no scope field and
92
+ the response states no scope, so never report a server-side scope, and never
93
+ claim the omission of `accountId` was recognised by Hub as a deliberate
94
+ content-only choice.
81
95
  3. Send `rewrite: true, attempt: 1`. Inspect `executionStatus` before
82
96
  `verdict`.
83
97
  For a comment/reply, include `thread.threadPermalink` when you know the
@@ -106,7 +120,14 @@ C-B/C-NB, and rows that were previously low risk.
106
120
  changing the family — only harder to notice. Apply only the local fixes, or
107
121
  resubmit stating the structural constraints. Submit the changed exact draft
108
122
  as `attempt: 2`.
109
- 5. Accept only `completed/pass` for content review. In ContentOnly, record
123
+ 5. Accept only `completed/pass` for content review. Report any dimension the
124
+ result marks `not_assessed` as NotAssessed with its `notAssessedReason` — it
125
+ was excluded from the score, its remaining `score` number is diagnostic only,
126
+ and quoting it as a result or averaging it misrepresents the review. In
127
+ ContentOnly, `persona_consistency` / `persona_voice` coming back
128
+ `not_assessed` is correct behaviour, not a degraded review. Full semantics,
129
+ including why scores are comparable only within one `applicabilitySignature`:
130
+ `../reddit-strategy-shared/references/content-review-gate.md`. In ContentOnly, record
110
131
  `ReviewMode: ContentOnly`, `AccountBindingStatus: Pending`,
111
132
  `ReadyForContentNextSkill: Yes`, and `ReadyForDelivery: No`;
112
133
  `accountCandidateFeedback` is advisory and persona consistency is not
@@ -152,6 +173,24 @@ then hand to rewrite to keep editing".
152
173
  - **Real limitations:** state genuine, decision-relevant limitations when they exist; never invent a safe flaw to look authentic; position and form follow the content, not a fixed paragraph.
153
174
  - **Punctuation and format values follow the subreddit baseline** — no cross-subreddit em-dash ban, no parenthesis quota, no fixed word count.
154
175
  - **ABU (Answer-Bearing Unit, B-group):** 1–2 per post, all 8 hard requirements met, `AnchorSlot` from the 7-value enum, visibility gate satisfied per length band. The old "must sit in paragraph 2 or 3" rule is removed.
176
+ - **The Persona Card is Brief-assigned; write inside it, never widen it.** Read
177
+ the Card behind the row's `PersonaCardRef` before drafting, and before handing
178
+ the draft to Hub review run the local boundary self-check
179
+ (`../reddit-strategy-shared/references/persona-card-contract.md` §8): every
180
+ firsthand experience claim maps to an entry in `permittedExperienceClaims`,
181
+ nothing displays knowledge listed in `doesNotKnow`, the stated relationship
182
+ matches `brandRelationshipDisclosed`, and `postingWhy` still coexists with the
183
+ assigned `PostingTrigger`. Record `PersonaCardSelfCheck: Pass | BlockingGap`
184
+ **per Key** in `PerKeyStructureContract`, alongside the `PersonaCardVersion` and
185
+ `PersonaCardHash` it was checked against, with the contradicting phrase and Card
186
+ field for each gap. A batch-level value alone cannot show that every post was
187
+ checked against its own current Card, which is what delivery verifies. A draft that needs a
188
+ wider boundary goes back to `reddit-phase-brief` for a new Card version — the
189
+ writer never edits the Card and never bumps its version. This check is local: it
190
+ produces no score, is not a review, and never substitutes for the Hub gate.
191
+ The Card does not relax `ClaimEvidenceStatus`; it adds *who may claim it* on top
192
+ of *whether it is sourced*. A firsthand phrase such as "mine arrived" is not an
193
+ offence by itself — it matters only when it contradicts the Card.
155
194
  - **`PostingTrigger` is Brief-pre-assigned, not writer-chosen.** After drafting,
156
195
  re-derive the actual `PostingTrigger` from the finished body and confirm it
157
196
  matches the `Planned` value; a mismatch is a `BlockingGap` (return to Brief),
@@ -214,14 +253,24 @@ then hand to rewrite to keep editing".
214
253
  - AssignedFlairVerified: Yes / No
215
254
  - PostingTriggerPlannedVsFinal: Match / Drift / NotAssigned
216
255
  - PostingTriggerCrossConstraintOK: Yes / No
256
+ - PersonaCardSelfCheck: Pass / BlockingGap / NotApplicable # batch-level = worst across Keys; per-Key values live in PerKeyStructureContract
257
+ - PersonaCardTransportStatus: LocalOnly(ServerContractMissing) # the Card was not sent to Hub
258
+ - PersonaCardAuthorizationStatus: Authorized / DeclaredOnly / NotApplicable # carried through from the Brief, not re-derived here
259
+ - AcceptanceProfile: Default / NativeCopy
260
+ - NativeCopyProfileStatus: NotSelected / Unavailable(ServerContractMissing)
261
+ - LocalReviewIntent: accountBinding=<account_bound|persona_less>, brandTreatment=<managed_brand|organic> # carried through from the Brief; local declaration, not a Hub receipt
262
+ - HubReviewScopeStatus: Unavailable(ServerContractMissing) # never report a server-side scope
263
+ - NotAssessedDimensions: # per row: dimension + notAssessedReason, verbatim from Hub
217
264
  - ClaimEvidenceAllCitedOrExternallyVerified: Yes / No # no Inferred claim reached any publishable text (title/body/comment/reply)
218
265
  - FingerprintIndexUpdated: Yes / No
219
266
  - CollisionCheckPassed: Yes / No / ExceptionRecorded
220
267
  - PerKeyStructureContract: |
221
268
  <one row per DraftKey — batch-level single values are not valid, a batch normally
222
269
  spans several families, anchor slots and hashes>
223
- | Key | ClientContentPrototype | ContentFunction | PrimaryPostType | StructureFamily | AssignedFlair | FlairCoverageStatus | PostingTrigger | SampleSetID | AnchorSlot | SecondaryAnchorSlot | AllowedStructuralChanges | ForbiddenStructuralChanges | DraftHash |
224
- |---|---|---|---|---|---|---|---|---|---|---|---|---|---|
270
+ <comment rows take N/A for PersonaCardRef / PersonaCardVersion /
271
+ PersonaCardHash / PersonaCardSelfCheck>
272
+ | Key | ClientContentPrototype | ContentFunction | PrimaryPostType | StructureFamily | AssignedFlair | FlairCoverageStatus | PostingTrigger | PersonaCardRef | PersonaCardVersion | PersonaCardHash | PersonaCardSelfCheck | SampleSetID | AnchorSlot | SecondaryAnchorSlot | AllowedStructuralChanges | ForbiddenStructuralChanges | DraftHash |
273
+ |---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
225
274
  - SKUBindingsPreserved: Yes / No
226
275
  - BrandWordStatusPreserved: Yes / No
227
276
  - ComplementRulesPassed: Yes / No
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: reddit-delivery-check
3
3
  description: |
4
- Reddit Phase 5 delivery QA skill. Use to verify final strategy deliverables, execution table coverage, batch draft completeness, SKU and brand-word quotas, external-comment audit status, brand-risk review status, unresolved placeholders, and the v2.12.25 structure gates before client delivery: client content-prototype mapping, StructureFamily preservation, rewrite structure drift, AnchorSlot legality, rolling-window structure collisions, subreddit baseline freshness, narrative-fingerprint / final-draft hash match, Hub optimized-draft structure override, and fabricated human signals (deliberate typos, fake community history, invented identity).
4
+ Reddit Phase 5 delivery QA skill. Use to verify final strategy deliverables, execution table coverage, batch draft completeness, SKU and brand-word quotas, external-comment audit status, brand-risk review status, unresolved placeholders, and the v2.12.25 structure gates before client delivery: client content-prototype mapping, StructureFamily preservation, rewrite structure drift, AnchorSlot legality, rolling-window structure collisions, subreddit baseline freshness, narrative-fingerprint / final-draft hash match, Hub optimized-draft structure override, fabricated human signals (deliberate typos, fake community history, invented identity), Persona Card preservation, correct reporting of not-assessed review dimensions, compliance-evidence freshness at delivery time, and acceptance-profile eligibility.
5
5
  ---
6
6
 
7
7
  # Reddit Delivery Check
@@ -18,6 +18,9 @@ Mandatory before running the structure gates:
18
18
 
19
19
  - `~/.cursor/skills/reddit-strategy-shared/references/content-type-routing-contract.md`
20
20
  - `~/.cursor/skills/reddit-strategy-shared/references/content-diversity-contract.md`
21
+ - `~/.cursor/skills/reddit-strategy-shared/references/persona-card-contract.md`
22
+ - `~/.cursor/skills/reddit-strategy-shared/references/content-review-gate.md` for how a `not_assessed` dimension must be reported and why scores are comparable only within one `applicabilitySignature`
23
+ - `~/.cursor/skills/reddit-strategy-shared/references/native-copy-profile.md` when the batch declared `AcceptanceProfile: NativeCopy`
21
24
 
22
25
  ## Required Inputs
23
26
 
@@ -35,7 +38,7 @@ Mandatory before running the structure gates:
35
38
  - verifier output when available
36
39
  - external post audit results
37
40
 
38
- ## Structure Checks (v2.12.25, all nine must pass)
41
+ ## Structure Checks (all ten must pass)
39
42
 
40
43
  Delivery is blocked unless every one of these verifies. This layer creates no
41
44
  diversity by itself; it exists so the earlier three layers cannot be skipped.
@@ -90,11 +93,25 @@ diversity by itself; it exists so the earlier three layers cannot be skipped.
90
93
  `this sub always...`), no invented identity, and no flaw inserted only to
91
94
  look authentic.
92
95
 
93
- **Scope.** Checks 1–7 apply to **main-post Keys only** (`WindowScope: PostOnly`).
96
+ 10. **Persona Card preserved.** Every delivered main-post Key's
97
+ `PersonaCardRef` / `PersonaCardVersion` / `PersonaCardHash` equal the Brief's
98
+ assigned Card, and `PersonaCardSelfCheck: Pass` was recorded **against that
99
+ same version**. A self-check recorded against an earlier version is stale, not
100
+ a pass — a Card change invalidates every boundary check made under the old
101
+ one. A Card authored or edited downstream of `reddit-phase-brief` is a
102
+ failure with `FixOwnerSkill: reddit-phase-brief`, whichever skill wrote it.
103
+ A missing Card on a post row is a `BlockingGap`, never "no Card, so nothing
104
+ to check". Carry `PersonaCardAuthorizationStatus` through from the Brief:
105
+ `DeclaredOnly` does not block delivery, but the delivery record may not
106
+ describe that row's firsthand claims as authorised. Do not read `RoleLabels`
107
+ in this check, and do not report the Card as a verified identity or as
108
+ anything Hub assessed.
109
+
110
+ **Scope.** Checks 1–7 and 10 apply to **main-post Keys only** (`WindowScope: PostOnly`).
94
111
  Comments, self-comments, and external comments need no fingerprint row, no
95
- prototype mapping, no `StructureFamily`, no `SampleSetID`, and no `AnchorSlot`;
96
- they still need Hub `completed/pass` plus `FinalDraftSha256`, and checks 8–9
97
- still apply to them.
112
+ prototype mapping, no `StructureFamily`, no `SampleSetID`, no `AnchorSlot`, and no
113
+ Persona Card; they still need Hub `completed/pass` plus `FinalDraftSha256`, and
114
+ checks 8–9 still apply to them.
98
115
 
99
116
  Missing index file, missing `Final` row for a post Key, zero or multiple active
100
117
  rows for one Key, unlocked window parameters, or a missing/expired baseline are
@@ -114,6 +131,10 @@ introduced by rewriting, set `FixOwnerSkill: reddit-content-rewrite`.
114
131
  - ContentOnly `completed/pass` is acceptable for a content handoff, but it is not a Phase 5 delivery pass and must show `AccountBindingStatus: Pending` and `ReadyForDelivery: No`.
115
132
  - No AccountBound delivered item has unresolved `brandFitReviewRequired=true` or blocking `accountCandidateFeedback`; the approved `brandContext` and resolution are recorded.
116
133
  - The review log covers every final draft, and any `revise` feedback is reflected in the exact delivered text. `degraded`, unavailable, or local-only review is a failure.
134
+ - Every dimension a Hub result marked `not_assessed` is carried into the delivery record as NotAssessed with its `notAssessedReason` — never as a low score, a passing score, or a partial check, and never averaged into any figure. The `score` left on such a dimension is diagnostic only. `unevaluatedDimensions` on a `degraded` result is a *different* thing (assessable but unanswered) and must not be reported as not-applicable. No delivery figure compares or averages scores across different `applicabilitySignature` values — a ContentOnly score and an AccountBound score are not the same measurement.
135
+ - Compliance evidence is still fresh **as of delivery**. Re-check the Compliance Output handoff's `EvidenceGatheredAt` / `EvidenceValidUntil` under the definition that handoff sets (`../reddit-strategy-shared/references/handoff-schemas.md`): they are batch-level **worst-case** scalars over the five delivery-required items (`RulesEvidenceStatus` / `PinnedEvidenceStatus` / `BotBannerEvidenceStatus` / `InstalledAppsStatus` / `AboutPageEvidenceStatus`) — earliest gathering time and earliest validity horizon. `EvidenceValidUntil` in the past, or `Unknown` on either scalar, holds the affected rows for re-gathering and re-judgement. A clearance recorded once at compliance time does not satisfy this.
136
+ - Separately from freshness: where the board's brand/vendor permission or a disclosure requirement was **never adjudicated**, it stays in `MissingEvidence` with the corresponding `PostRisk` / `CommentRisk` and needs a recorded human decision. This is not a freshness question and no timestamp resolves it — re-gathering evidence re-opens the question, it never answers it, and the answer is never inferred from a style profile's brand-friendliness note or from raw collected evidence.
137
+ - When the batch declared `AcceptanceProfile: NativeCopy`, `NativeCopyProfileStatus` is recorded and no row is `ReadyForDelivery: Yes` while it is `Unavailable(ServerContractMissing)`. A batch that was quietly re-labelled `Default` to release delivery, or whose review call was weakened until it succeeded and then counted as a profile pass, is a failure.
117
138
  - No placeholders, unresolved evidence gaps, or unmerged side tables remain.
118
139
  - Master Canvas has all 21 boards, including Data Support, MCP Status, and Verification Boundaries.
119
140
  - Feishu draft delivery uses a single draft document unless the user explicitly requests otherwise.
@@ -138,6 +159,10 @@ introduced by rewriting, set `FixOwnerSkill: reddit-content-rewrite`.
138
159
  - FingerprintHashIssues:
139
160
  - HubStructureConflictIssues:
140
161
  - FabricatedSignalIssues:
162
+ - PersonaCardIssues:
163
+ - NotAssessedReportingIssues:
164
+ - EvidenceFreshnessIssues:
165
+ - AcceptanceProfileIssues:
141
166
  - PlaceholderIssues:
142
167
  - RequiredFixes:
143
168
  - DeliverySummary:
@@ -160,6 +185,13 @@ introduced by rewriting, set `FixOwnerSkill: reddit-content-rewrite`.
160
185
  - FingerprintHashStatus: Matched / Mismatch / Pending
161
186
  - HubStructureConflictStatus: None / Escalated
162
187
  - FabricatedSignalStatus: None / Found
188
+ - PersonaCardStatus: Preserved / Drift / Missing / NotApplicable
189
+ - PersonaCardAuthorizationStatus: Authorized / DeclaredOnly / NotApplicable # carried through from the Brief; DeclaredOnly does not block but may not be reported as authorised
190
+ - EvidenceFreshnessStatus: Fresh / Expired / Unknown
191
+ - AcceptanceProfile: Default / NativeCopy
192
+ - NativeCopyProfileStatus: NotSelected / Unavailable(ServerContractMissing)
193
+ - LocalReviewIntent: accountBinding=<account_bound|persona_less>, brandTreatment=<managed_brand|organic> # carried through; local declaration only
194
+ - HubReviewScopeStatus: Unavailable(ServerContractMissing)
163
195
  - ReviewMode: ContentOnly | AccountBound
164
196
  - AccountBindingStatus: Pending | Resolved | NeedsReview
165
197
  - ReadyForDelivery: Yes / No
@@ -7,7 +7,7 @@
7
7
  "contentSha256": "9a4fa3944ec3913a4e68cb1faf4d4a32ca04131fa4b0c37e054065f4e2c1a1ed"
8
8
  },
9
9
  "reddit-strategy-shared": {
10
- "contentSha256": "05433f0686955304b3ccaf728aacead4680a9b89a9adff4955111c837a441c4a"
10
+ "contentSha256": "bba5c177a4af9bab0f2d9c227993862629e82ec9747cd2788e4874d058da3c89"
11
11
  },
12
12
  "reddit-phase-research": {
13
13
  "contentSha256": "9f47b8eb0d1c1c194c685856242c9b22d190dfa8c6f572869e964239215795fe"
@@ -16,16 +16,16 @@
16
16
  "contentSha256": "8080adce62b6db65be3dbc0f37f162a81e382d6b64108f7352ec83c356664659"
17
17
  },
18
18
  "reddit-phase-brief": {
19
- "contentSha256": "6030d98d515355526e63d89246845dd18f0d0d2b3a2dbf71a2e9317eae31f54b"
19
+ "contentSha256": "3475bf4c4aa4796db1c99a3bd527c52dc8488f71db01753da0325e924d552b99"
20
20
  },
21
21
  "reddit-content-writing": {
22
- "contentSha256": "b3a2219d2a328f76413cea34f0dfaa8588da635e3d644781d124e4d3b2a92e03"
22
+ "contentSha256": "08023a5dfa6cc2f3ffc3f1a551479c645ddcfc8cc775f88cd2d245c067829482"
23
23
  },
24
24
  "reddit-content-rewrite": {
25
- "contentSha256": "3e7df833877fc81a46d66ab7c827fac386a1413862724179e87570d773ab8935"
25
+ "contentSha256": "2f9dfdbb2410d060f755860b57ebc7c6776f4df343990029a8770855ecd33005"
26
26
  },
27
27
  "reddit-delivery-check": {
28
- "contentSha256": "690609ca874e259800d02dbeebd707546d432bc24525a7c65ace0e0a7633ee78"
28
+ "contentSha256": "9231ff9ae5d950bc2fb87e45bee05e793b3ff9c37bee45e8444059a8b698e4a9"
29
29
  },
30
30
  "reddit-voc-volume": {
31
31
  "contentSha256": "496c0d57af7507d86a16bd4d06f3a5a017ca10b3dc4e3564d82b0c988e1c87ac"
@@ -34,7 +34,7 @@
34
34
  "contentSha256": "dbba80345ebe68818a25e7604dfaa6951c6bf0e4d785d5013be7a06b22ec5b7c"
35
35
  },
36
36
  "reddit-subreddit-compliance": {
37
- "contentSha256": "a996e1dbffee237d0535012ac3ec40698c2d4ba03d0c9fb088fefe27f51646ee"
37
+ "contentSha256": "3cab43d1c70d511212f3684929a8d7da73c11d3ae34563f31d1eebe04a6ecd8b"
38
38
  },
39
39
  "reddit-answers-research": {
40
40
  "contentSha256": "eb132d81021f0a24619d9b576a287f722cf61c24422874d5be86cd4be2c392df"
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: reddit-phase-brief
3
3
  description: |
4
- Reddit Phase 4 Brief skill for the matrix SOP. Use after reddit-phase-planning completes Phase 4.0.6 and before reddit-content-writing starts. It locks the target flair first by verifying planning's `ProposedFlair` from the scheduling ledger, then establishes the flair-level subreddit baseline, then locks ContentFunction and StructureFamily, then the outline; it also creates board rationale, content schedule, per-Key title/direction/outlines, brand/SKU placement strategy, prewriting control fields (AssignedFlair / PostingTrigger), structure pre-assignment (OpeningMove / EvidenceShape / BrandEntryMode / EndingMode / LengthBand / SampleSetID / RecentCollisionCheck), and the approval gate. Trigger when the user says "确认进入 Phase 4 Brief", "开始 Brief", "产出 Brief 汇报稿", or when planning is complete but drafting has not started.
4
+ Reddit Phase 4 Brief skill for the matrix SOP. Use after reddit-phase-planning completes Phase 4.0.6 and before reddit-content-writing starts. It locks the target flair first by verifying planning's `ProposedFlair` from the scheduling ledger, then establishes the flair-level subreddit baseline, then locks ContentFunction and StructureFamily, then the outline; it also creates board rationale, content schedule, per-Key title/direction/outlines, brand/SKU placement strategy, prewriting control fields (AssignedFlair / PostingTrigger / PersonaCardRef), structure pre-assignment (OpeningMove / EvidenceShape / BrandEntryMode / EndingMode / LengthBand / SampleSetID / RecentCollisionCheck), and the approval gate. Trigger when the user says "确认进入 Phase 4 Brief", "开始 Brief", "产出 Brief 汇报稿", or when planning is complete but drafting has not started.
5
5
  ---
6
6
 
7
7
  # Reddit Phase Brief
@@ -17,6 +17,8 @@ Read the shared contracts first:
17
17
  - `~/.cursor/skills/reddit-strategy-shared/references/context-contract.md`
18
18
  - `~/.cursor/skills/reddit-strategy-shared/references/content-type-routing-contract.md` (mandatory)
19
19
  - `~/.cursor/skills/reddit-strategy-shared/references/content-diversity-contract.md` (mandatory)
20
+ - `~/.cursor/skills/reddit-strategy-shared/references/persona-card-contract.md` (mandatory — this skill **owns** the Persona Card)
21
+ - `~/.cursor/skills/reddit-strategy-shared/references/native-copy-profile.md` only when the batch runs `AcceptanceProfile: NativeCopy`
20
22
 
21
23
  Then verify these project memory files exist:
22
24
 
@@ -95,9 +97,26 @@ outline before the structure is decided from evidence.
95
97
  7. **Lock the project diversity window** (`WindowCount` 8–12, `WindowDays` 30–60) if not already locked, and read the rolling window from `memory/phase4-叙事指纹索引.md`.
96
98
  8. Produce board-selection rationale with evidence URLs, then the board overview table.
97
99
  8b. **Run the material-modality preflight (G4, step B-1.4) before locking any structure.** Every selling point / material carries `MaterialID` + `OriginalModality` (`Required` / `Preferred` / `Optional` / `Forbidden`) + `EffectiveModality` + `ModalitySource`. Upgrading modality is blocked unless an `AuthoritativeSource` (`ClientContract` / `SubredditRule` / `ComplianceRequirement` / an already-authorized `ApprovedBrief`) backs it via `ModalityUpgradeRef`, with `ApprovedBy` ≠ the generating agent. **A material the user explicitly marked `Optional` is never upgraded** — go back to the user instead. Missing or conflicting modality is fail-closed. Disclosure/compliance requirements are not selling-point modality.
98
- 9. For every post Key: resolve `客户内容原型` → `MappingRuleID`, lock `ContentFunction`, confirm the `EvidenceShape` actually in hand, narrow `StructureFamily` candidates with the ordered filter in `content-type-routing-contract.md`, pre-assign `OpeningMove` / `CaveatMode` / `EndingMode` / `LengthBand` / `AnchorSlot` (**`BrandEntryMode` is deliberately excluded here — it is assigned in 9b, after its gate**), and **record (not yet validate)** the `PostingTrigger` candidate — its cross-constraint with `BrandEntryMode` can only be checked after 9b(受控值 `ConcreteFailure` / `PurchaseDecision` / `MeasurementResult` / `CompatibilityGap` / `UnsolicitedTestimony`;`UnsolicitedTestimony` 仅当 `BrandEntryMode: 不出现` 且有真实非征集非品牌控制的 testimony 证据),and **only then** — after 9b has supplied `BrandEntryMode` — run `RecentCollisionCheck` on the complete triple. On collision, first swap `OpeningMove` / `EndingMode` within the same family; reselect a family only if that fails. **Only then** write the outline.
100
+ 9. For every post Key: resolve `客户内容原型` → `MappingRuleID`, lock `ContentFunction`, confirm the `EvidenceShape` actually in hand, narrow `StructureFamily` candidates with the ordered filter in `content-type-routing-contract.md`, pre-assign `OpeningMove` / `CaveatMode` / `EndingMode` / `LengthBand` / `AnchorSlot` (**`BrandEntryMode` is deliberately excluded here — it is assigned in 9b, after its gate**), and **record (not yet validate)** the `PostingTrigger` candidate — its cross-constraint with `BrandEntryMode` can only be checked after 9b(受控值 `ConcreteFailure` / `PurchaseDecision` / `MeasurementResult` / `CompatibilityGap` / `UnsolicitedTestimony`;`UnsolicitedTestimony` 仅当 `BrandEntryMode: 不出现` 且有真实非征集非品牌控制的 testimony 证据),and **only then** — after 9b has supplied `BrandEntryMode` — run `RecentCollisionCheck` on the complete triple. On collision, first swap `OpeningMove` / `EndingMode` within the same family; reselect a family only if that fails. **Only then** write the outline. Under `AcceptanceProfile: NativeCopy` a client requirement about how posts end narrows **the set this step may assign from** — it is never a question appended to a finished draft, since `EndingMode` is a locked field and one of the three collision fields. If the narrowed set leaves no collision-free option, that is a `BlockingGap` back to planning (`../reddit-strategy-shared/references/native-copy-profile.md`).
99
101
  9b. **Only now assign `BrandEntryMode`, and only through `BrandPlacementGate` (G2):** for **P-B / PGC posts**, placement is decided by the brand-first-mention histogram plus the §2 risk ceiling (`SelectedPlacement` = the later of the two, histogram bucket 结论 mapping to 后段; the domain is 标题/首段/中段/后段, and a class-D board or `NoBrandMentionRate ≥ 0.8` that leaves no viable spot is a `BlockingGap` back to planning, never a silent drop to 不出现/评论) — **never derived from the P-B label**. **P-NB posts** go straight to `不出现` without running the formula; **C-B comments** use `CommentPlacementDecision` plus the §2 ceiling and complement rules. Fill the placement evidence first, **branching by row type**: P-B / PGC posts need a complete `BrandPlacementBasis`; P-NB posts take `N/A(P-NB · 主帖不露品牌)`; C-B comments take `InheritedBrandPlacementBasis` + `CommentPlacementDecision` instead and are never required to carry their own. Legitimate title exposure is preserved for `PGCApproved` (precedent check passed, board allows vendor/official, flair/disclosure/frequency met) and `BrandReviewPost` (genuinely a post about that brand, judged from `PrimaryPostType` / `WritingVariant` plus topic evidence — never by inventing a `ContentFunction: Review` value). This gate governs *what decides placement only* — brand quota semantics (P-B / P-NB / C-B / C-NB / PGC) are untouched.
100
102
  9c. **Narrative layering (G1, step B-1.6) before writing title directions or outlines:** every post and comment carries `InternalCoverageIntent` (FAQ ids, fanout queries, keywords, gap codes, `prompt_id` — internal only) and a non-empty `ReaderVisiblePremise`. The gate scans only reader-facing fields (核心角度 / 标题方向 / 大纲 / 撰写逻辑一句话 / 评论任务方向 / outbound Reddit payload); Routing Decision, TraceFields and 方向选取理由 keep their internal labels. Converting FAQ evidence into a real reader question is the intended outcome — only raw internal labels in the narrative are blocked.
103
+ 9d. **Assign the Persona Card for every main-post Key (contract:
104
+ `../reddit-strategy-shared/references/persona-card-contract.md`).** The Card is the
105
+ third `PrewritingControlField` and this skill owns it: record `PersonaCardRef` +
106
+ `PersonaCardVersion` + `PersonaCardHash`, the capability fields
107
+ (`knowledgeAccess` — exactly one of `research` / `owned` / `used` / `observed` /
108
+ `supplied_by_brand`; `permittedExperienceClaims` — a possibly-empty subset of
109
+ `{ownership, delivery, wear, service}`; `attentionFocus`; `questionIntent`), and the
110
+ per-draft fields (`postingWhy`, `knows[]`, `doesNotKnow[]`, `permittedFactRefs`,
111
+ `brandRelationshipDisclosed`). `postingWhy` must be able to coexist with the assigned
112
+ `PostingTrigger` — they are different fields and neither substitutes for the other.
113
+ **The writer never authors a Card**; letting it do so would let the draft grant itself
114
+ whatever boundary it already needs. Comments / self-comments / external comments take
115
+ `N/A` — this train does not put comments under a Card. `permittedExperienceClaims` is
116
+ authorised from submitted context, never inferred from the brand relationship: a
117
+ partner may genuinely own the product, and an empty set is a normal Card. The Card is a
118
+ **declared posting context, not a verified identity** — it is `LocalOnly`, is never
119
+ sent to Hub, and never implies the publishing account was resolved.
101
120
  10. Create one Brief block for every execution-table Key in the selected batch, including posts, self-comments, and external comments.
102
121
  11. For all P-B/C-B/PGC rows, record brand/SKU placement rationale using `references/brand-SKU-PGC-rules.md` (§3's five sample-learning classes are *rationale records only* — they never write `SelectedPlacement` directly nor bypass §2.0's mechanical rules).
103
122
  12. Write a `Planned` fingerprint row for every briefed post Key into `memory/phase4-叙事指纹索引.md` (`DraftHash: PENDING`), including the two `PrewritingControlFields` (`AssignedFlair`, `PostingTrigger`).
@@ -115,6 +134,14 @@ Baseline cards may live in `memory/phase4-撰写brief.md` or a dedicated baselin
115
134
  file; either way each is addressed by its `SampleSetID` and is reusable across
116
135
  Keys and batches within its validity period.
117
136
 
137
+ Persona Cards may likewise live inside `memory/phase4-撰写brief.md` or in a
138
+ dedicated `memory/phase4-persona-card.md`; either way each is addressed by its
139
+ `PersonaCardRef` and carries its own `PersonaCardVersion` / `PersonaCardHash`.
140
+ Unlike a baseline card a Persona Card is **per post Key, not shared** — its
141
+ per-draft fields belong to one submission. Do not record Cards in
142
+ `memory/phase4-叙事指纹索引.md`: the index has a fixed schema and a fixed hash
143
+ definition, and the Card is not a fingerprint field.
144
+
118
145
  Optional Feishu Brief documents require an explicit user-provided folder link.
119
146
 
120
147
  ## Gate
@@ -124,7 +151,8 @@ Writing may start only after:
124
151
  - Every target subreddit has a valid, unexpired baseline card with a `SampleSetID`, with flair coverage recorded (`BaselineScope` / `FlairCoverageStatus`); `FlairCoverageStatus` is `Exact` or `ApprovedFallback` (never `Insufficient`).
125
152
  - Every **main-post** Key carries a non-empty `AssignedFlair` — verified against the ledger's `ProposedFlair` and the subreddit's live flair list (`FlairVerificationStatus: Verified` or `FallbackAssigned`; no `Rejected` left open), or the sentinel `NoFlairAvailable` / `NoFlairRequired` for flairless boards — and its `AssignedFlair` matches the baseline card's covered flair (or an approved fallback).
126
153
  - Every **main-post** Key's `客户内容原型` resolves to an `Approved` mapping row (`MappingRuleID` / `MappingVersion` recorded in the Routing Decision). Self-comments inherit the paired post's value; external comments use `—` and need no mapping of their own.
127
- - Every post Brief row carries the nine `LockedFingerprintFields` (`ContentFunction`, `StructureFamily`, `OpeningMove`, `EvidenceShape`, `BrandEntryMode`, `CaveatMode`, `EndingMode`, `LengthBand`, `AnchorSlot`), the two `PrewritingControlFields` (`AssignedFlair`, `PostingTrigger` — the latter satisfying the `PostingTrigger × BrandEntryMode` cross-constraint), and the four `TraceFields` (`SampleSetID`, `RecentCollisionCheck`, `MappingRuleID`, `MappingVersion`).
154
+ - Every post Brief row carries the nine `LockedFingerprintFields` (`ContentFunction`, `StructureFamily`, `OpeningMove`, `EvidenceShape`, `BrandEntryMode`, `CaveatMode`, `EndingMode`, `LengthBand`, `AnchorSlot`), the three `PrewritingControlFields` (`AssignedFlair`, `PostingTrigger` — satisfying the `PostingTrigger × BrandEntryMode` cross-constraint — and `PersonaCardRef`), and the four `TraceFields` (`SampleSetID`, `RecentCollisionCheck`, `MappingRuleID`, `MappingVersion`).
155
+ - Every **main-post** Key carries a non-empty `PersonaCardRef` with its `PersonaCardVersion` / `PersonaCardHash`, a single `knowledgeAccess`, a `permittedExperienceClaims` set drawn only from `{ownership, delivery, wear, service}` (empty is valid), non-empty `postingWhy`, and a `postingWhy` that can coexist with the assigned `PostingTrigger`. Comments take `N/A`. Role labels, if present, are non-authoritative and no gate reads them. Any Card with a non-empty `permittedExperienceClaims` also carries `DeclaredBy` (never the generating agent) and `ClaimAuthorizationRef`; without both, the claim set is reported as a Brief declaration rather than an authorised set.
128
156
  - `RecentCollisionCheck` is `Pass` for every row, or `Exception` with an approved `ExceptionRef`.
129
157
  - Every **P-B / PGC post** row carries a complete `BrandPlacementBasis` and a `SelectedPlacement` consistent with it (an already-planned P-B/PGC row may only land on 标题/首段/中段/后段 — moving the brand to a comment or dropping it is a quota change, so it goes back to planning); **C-B comments** carry `InheritedBrandPlacementBasis` + `CommentPlacementDecision` instead and never build their own histogram. No row derives title-brand exposure from the P-B label; `NoComparableEvidence` rows went through re-sampling / StopAndAsk / a recorded `BrandTitleExposureException`, not a default; `Sparse` rows meet the three §2.5 conditions.
130
158
  - Every post and comment Brief carries `InternalCoverageIntent` + a non-empty `ReaderVisiblePremise`, and no reader-facing field leaks internal labels.
@@ -154,8 +182,15 @@ Writing may start only after:
154
182
  - FlairCoverageStatus: Exact / ApprovedFallback / Insufficient # batch-level = worst status across cards (Insufficient > ApprovedFallback > Exact)
155
183
  - ClientPrototypeMappingStatus: AllApproved / BlockingGap
156
184
  - StructureFieldsAssigned: Yes / No
157
- - PrewritingControlFieldsAssigned: Yes / No # AssignedFlair + PostingTrigger per main-post Key
185
+ - PrewritingControlFieldsAssigned: Yes / No # AssignedFlair + PostingTrigger + PersonaCardRef per main-post Key
158
186
  - PostingTriggerCrossConstraintOK: Yes / No # UnsolicitedTestimony ⇒ BrandEntryMode 不出现
187
+ - PersonaCardCoverage: <n>/<N> # main-post Keys with a non-empty PersonaCardRef
188
+ - PersonaCardTransportStatus: LocalOnly(ServerContractMissing) # the Card is never sent to Hub
189
+ - PersonaCardAuthorizationStatus: Authorized / DeclaredOnly / NotApplicable # worst status across Keys; propagate to writing and delivery
190
+ - AcceptanceProfile: Default / NativeCopy
191
+ - NativeCopyProfileStatus: NotSelected / Unavailable(ServerContractMissing)
192
+ - LocalReviewIntent: accountBinding=<account_bound|persona_less>, brandTreatment=<managed_brand|organic> # local planner declaration; propagate to writing and delivery, never sent to Hub
193
+ - HubReviewScopeStatus: Unavailable(ServerContractMissing) # no server-side scope exists to report
159
194
  - FingerprintPlannedRows: <n>/<N>
160
195
  - DiversityWindow: WindowCount=<n>, WindowDays=<n>
161
196
  - RecentCollisionCheck: Pass / BlockingCollision / Exception(<ExceptionRef>)