@geoly-ai/social-hub-cli 0.3.18 → 0.3.20
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/CHANGELOG.md +29 -0
- package/dist/cmd-manifest.json +4 -2
- package/dist/content-review.test.js +1 -1
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +98 -8
- package/dist/index.js.map +1 -1
- package/dist/register-calendar-directives.d.ts +64 -0
- package/dist/register-calendar-directives.d.ts.map +1 -0
- package/dist/register-calendar-directives.js +135 -0
- package/dist/register-calendar-directives.js.map +1 -0
- package/dist/register-calendar-directives.test.d.ts +2 -0
- package/dist/register-calendar-directives.test.d.ts.map +1 -0
- package/dist/register-calendar-directives.test.js +127 -0
- package/dist/register-calendar-directives.test.js.map +1 -0
- package/dist/scrape-output.test.d.ts +2 -0
- package/dist/scrape-output.test.d.ts.map +1 -0
- package/dist/scrape-output.test.js +58 -0
- package/dist/scrape-output.test.js.map +1 -0
- package/package.json +2 -2
- package/skills/README.md +23 -22
- package/skills/manifest.json +6 -1
- package/skills/reddit-content-rewrite/SKILL.md +29 -0
- package/skills/reddit-content-writing/SKILL.md +54 -5
- package/skills/reddit-content-writing/references/phase4-execution-table.md +24 -3
- package/skills/reddit-content-writing/references/phase4-writing.md +123 -3
- package/skills/reddit-content-writing/references/post-writing-templates.md +14 -6
- package/skills/reddit-delivery-check/SKILL.md +39 -6
- package/skills/reddit-delivery-check/references/phase5-delivery.md +7 -1
- package/skills/reddit-matrix.lock.json +10 -10
- package/skills/reddit-phase-brief/SKILL.md +62 -11
- package/skills/reddit-phase-brief/references/brand-SKU-PGC-rules.md +223 -36
- package/skills/reddit-phase-brief/references/phase4-brief-workflow.md +405 -19
- package/skills/reddit-phase-planning/SKILL.md +13 -0
- package/skills/reddit-phase-planning/references/phase4-execution-table.md +16 -2
- package/skills/reddit-phase-planning/references/phase4-planning.md +74 -15
- package/skills/reddit-phase-planning/references/scheduling-decision-procedure.md +386 -0
- package/skills/reddit-phase-research/SKILL.md +7 -1
- package/skills/reddit-phase-research/references/phase01-research.md +93 -29
- package/skills/reddit-strategy-orchestrator/SKILL.md +120 -18
- package/skills/reddit-strategy-shared/SKILL.md +4 -2
- package/skills/reddit-strategy-shared/references/content-diversity-contract.md +26 -5
- package/skills/reddit-strategy-shared/references/content-review-gate.md +48 -1
- package/skills/reddit-strategy-shared/references/context-contract.md +45 -11
- package/skills/reddit-strategy-shared/references/evidence-policy.md +139 -0
- package/skills/reddit-strategy-shared/references/handoff-schemas.md +88 -3
- package/skills/reddit-strategy-shared/references/native-copy-profile.md +207 -0
- package/skills/reddit-strategy-shared/references/path-conventions.md +26 -4
- package/skills/reddit-strategy-shared/references/persona-card-contract.md +210 -0
- package/skills/reddit-strategy-shared/references/prerequisites/README.md +57 -0
- package/skills/reddit-strategy-shared/references/prerequisites/{reddit-master-canvas-agent-prompt-template.md → archive/reddit-master-canvas-agent-prompt-template.md} +7 -0
- package/skills/reddit-strategy-shared/references/prerequisites/{reddit-sop-v2-universal.md → archive/reddit-sop-v2-universal.md} +7 -0
- package/skills/reddit-strategy-shared/references/prerequisites/{reddit-strategy-prerequisites-read.md → archive/reddit-strategy-prerequisites-read.md} +7 -0
- package/skills/reddit-strategy-shared/references/prerequisites/{reddit-strategy-reuse-playbook.md → archive/reddit-strategy-reuse-playbook.md} +7 -0
- package/skills/reddit-strategy-shared/references/subreddit-research-profile.md +514 -0
- package/skills/reddit-strategy-shared/references/version-manifest.md +89 -2
- package/skills/reddit-subreddit-compliance/SKILL.md +28 -1
- package/skills/reddit-voc-volume/matrix-contract.md +16 -1
- package/skills/social-hub-calendar-directives/SKILL.md +548 -0
- package/skills/social-hub-calendar-jobs/SKILL.md +10 -0
- package/skills/social-hub-cli/SKILL.md +2 -0
- package/skills/social-hub-notifications/SKILL.md +7 -0
- package/skills/social-hub-publishing/SKILL.md +5 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
/**
|
|
3
|
+
* `social-hub calendar preflight` / `social-hub calendar set-delivery-mode`
|
|
4
|
+
* —— 内容发布日历指令(`publishing-calendar-directive.v1`)的 CLI 交付面。
|
|
5
|
+
*
|
|
6
|
+
* 设计与冻结决定见 `docs/publishing-calendar-directive-plan.md` §8(D1–D6),
|
|
7
|
+
* 消费协议见 `skills/social-hub-calendar-directives/SKILL.md`。
|
|
8
|
+
*
|
|
9
|
+
* ## 两条不可协商的形态约束
|
|
10
|
+
*
|
|
11
|
+
* 1. **preflight 的业务拒绝不是错误。** 服务端刻意让「条目已取消 / 审核被撤回 /
|
|
12
|
+
* 版本过期」走 HTTP 200 + `decision:"rejected"`,好让 cron 路径无歧义分流每个
|
|
13
|
+
* `reason`。因此本命令**默认恒 exit 0**,先把完整 JSON 写到 stdout;只有显式
|
|
14
|
+
* `--require-ok` 才在非 ok 时补一个 exit 1。反过来做(默认非零)会让所有
|
|
15
|
+
* `set -e` 的脚本在「条目正常取消了」这种日常路径上直接崩掉。
|
|
16
|
+
*
|
|
17
|
+
* 2. **切 deliveryMode 必须过 `--apply` 门。** 它是防重复发帖的唯一闸门(D1):
|
|
18
|
+
* 服务端会在同一事务里撤销/创建内部 job 并 append 指令。手滑改错的后果是
|
|
19
|
+
* 同一条内容被发两遍,或者谁都不发 —— 这个量级的写操作不该只靠一个 `--mode`。
|
|
20
|
+
* `calendar patch --body '{"deliveryMode":...}'` 同样被这道门挡住(见
|
|
21
|
+
* {@link deliveryModeChangeRequiresApply}),否则新命令的护栏只是装饰。
|
|
22
|
+
*/
|
|
23
|
+
/** 契约 `calendarEntryDeliveryModeSchema` 的镜像;真源在 contracts。 */
|
|
24
|
+
declare const DELIVERY_MODES: readonly ["hub_managed", "external_agent"];
|
|
25
|
+
export type DeliveryModeArg = (typeof DELIVERY_MODES)[number];
|
|
26
|
+
export declare function isDeliveryMode(value: unknown): value is DeliveryModeArg;
|
|
27
|
+
export declare class CalendarDirectiveArgError extends Error {
|
|
28
|
+
constructor(message: string);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* `--revision` 的本地解析。
|
|
32
|
+
*
|
|
33
|
+
* 两道校验缺一不可:
|
|
34
|
+
* - `^\d+$` 挡住 `""` / `" "` / `1e3` / `-1` / `1.5`。**绝不能用 `Number()` 兜底**:
|
|
35
|
+
* 它把空串和空白都转成 `0`,而 revision 0 是每条条目的合法初始值 —— 于是
|
|
36
|
+
* 「忘了带版本号」会被服务端当成「我手上是第 0 版」而真的放行内容。
|
|
37
|
+
* - `Number.isSafeInteger` 挡住 `99999999999999999999` 这类纯数字但超出 IEEE754
|
|
38
|
+
* 安全区的值:它能过正则,转成 number 后却已经悄悄变了值,比对上的是另一条
|
|
39
|
+
* revision。服务端也有 `MAX_SAFE_INTEGER` 上界,这里提前挡住只是不让明显非法
|
|
40
|
+
* 的值打到网络上。
|
|
41
|
+
*/
|
|
42
|
+
export declare function parseRevisionArg(raw: string): number;
|
|
43
|
+
/**
|
|
44
|
+
* `calendar patch` 的 body 是不是在动 `deliveryMode` —— 是就必须过 `--apply` 门。
|
|
45
|
+
*
|
|
46
|
+
* 只看「键在不在」而不看值:`{"deliveryMode": null}` / 写错的枚举值同样是一次
|
|
47
|
+
* 「意图切换执行主体」的调用,服务端会拒,但它该在拒之前先被护栏拦下。
|
|
48
|
+
*/
|
|
49
|
+
export declare function deliveryModeChangeRequiresApply(body: Record<string, unknown>): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* `calendar patch` 要不要进 `assertApplyOrDryRun`。
|
|
52
|
+
*
|
|
53
|
+
* 两个判据故意不同:
|
|
54
|
+
* - `--dry-run` **对任何 body 都生效** —— 全局 flag 契约是「预览写入,不调用 API」。
|
|
55
|
+
* 只在 deliveryMode 分支上认它,就会出现「加了 --dry-run 却真的写了」的意外写入。
|
|
56
|
+
* - `--apply` 只在 body 含 `deliveryMode` 时才是必填(那是 D1 闸门);其余字段
|
|
57
|
+
* (status / permalink / …)维持既有的无门行为,不破坏存量自动化。
|
|
58
|
+
*/
|
|
59
|
+
export declare function calendarPatchNeedsGate(body: Record<string, unknown>, opts: {
|
|
60
|
+
dryRun?: boolean;
|
|
61
|
+
}): boolean;
|
|
62
|
+
export declare function registerCalendarDirectiveCommands(calendar: Command): void;
|
|
63
|
+
export {};
|
|
64
|
+
//# sourceMappingURL=register-calendar-directives.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register-calendar-directives.d.ts","sourceRoot":"","sources":["../src/register-calendar-directives.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIzC;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,8DAA8D;AAC9D,QAAA,MAAM,cAAc,4CAA6C,CAAC;AAClE,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9D,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,eAAe,CAKvE;AAED,qBAAa,yBAA0B,SAAQ,KAAK;gBACtC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAapD;AAED;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAET;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,IAAI,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GACzB,OAAO,CAET;AAED,wBAAgB,iCAAiC,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAkFzE"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { requireClient, resolveTeamId } from "./client.js";
|
|
2
|
+
import { assertApplyOrDryRun } from "./dry-run.js";
|
|
3
|
+
/**
|
|
4
|
+
* `social-hub calendar preflight` / `social-hub calendar set-delivery-mode`
|
|
5
|
+
* —— 内容发布日历指令(`publishing-calendar-directive.v1`)的 CLI 交付面。
|
|
6
|
+
*
|
|
7
|
+
* 设计与冻结决定见 `docs/publishing-calendar-directive-plan.md` §8(D1–D6),
|
|
8
|
+
* 消费协议见 `skills/social-hub-calendar-directives/SKILL.md`。
|
|
9
|
+
*
|
|
10
|
+
* ## 两条不可协商的形态约束
|
|
11
|
+
*
|
|
12
|
+
* 1. **preflight 的业务拒绝不是错误。** 服务端刻意让「条目已取消 / 审核被撤回 /
|
|
13
|
+
* 版本过期」走 HTTP 200 + `decision:"rejected"`,好让 cron 路径无歧义分流每个
|
|
14
|
+
* `reason`。因此本命令**默认恒 exit 0**,先把完整 JSON 写到 stdout;只有显式
|
|
15
|
+
* `--require-ok` 才在非 ok 时补一个 exit 1。反过来做(默认非零)会让所有
|
|
16
|
+
* `set -e` 的脚本在「条目正常取消了」这种日常路径上直接崩掉。
|
|
17
|
+
*
|
|
18
|
+
* 2. **切 deliveryMode 必须过 `--apply` 门。** 它是防重复发帖的唯一闸门(D1):
|
|
19
|
+
* 服务端会在同一事务里撤销/创建内部 job 并 append 指令。手滑改错的后果是
|
|
20
|
+
* 同一条内容被发两遍,或者谁都不发 —— 这个量级的写操作不该只靠一个 `--mode`。
|
|
21
|
+
* `calendar patch --body '{"deliveryMode":...}'` 同样被这道门挡住(见
|
|
22
|
+
* {@link deliveryModeChangeRequiresApply}),否则新命令的护栏只是装饰。
|
|
23
|
+
*/
|
|
24
|
+
/** 契约 `calendarEntryDeliveryModeSchema` 的镜像;真源在 contracts。 */
|
|
25
|
+
const DELIVERY_MODES = ["hub_managed", "external_agent"];
|
|
26
|
+
export function isDeliveryMode(value) {
|
|
27
|
+
return (typeof value === "string" &&
|
|
28
|
+
DELIVERY_MODES.includes(value));
|
|
29
|
+
}
|
|
30
|
+
export class CalendarDirectiveArgError extends Error {
|
|
31
|
+
constructor(message) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.name = "CalendarDirectiveArgError";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* `--revision` 的本地解析。
|
|
38
|
+
*
|
|
39
|
+
* 两道校验缺一不可:
|
|
40
|
+
* - `^\d+$` 挡住 `""` / `" "` / `1e3` / `-1` / `1.5`。**绝不能用 `Number()` 兜底**:
|
|
41
|
+
* 它把空串和空白都转成 `0`,而 revision 0 是每条条目的合法初始值 —— 于是
|
|
42
|
+
* 「忘了带版本号」会被服务端当成「我手上是第 0 版」而真的放行内容。
|
|
43
|
+
* - `Number.isSafeInteger` 挡住 `99999999999999999999` 这类纯数字但超出 IEEE754
|
|
44
|
+
* 安全区的值:它能过正则,转成 number 后却已经悄悄变了值,比对上的是另一条
|
|
45
|
+
* revision。服务端也有 `MAX_SAFE_INTEGER` 上界,这里提前挡住只是不让明显非法
|
|
46
|
+
* 的值打到网络上。
|
|
47
|
+
*/
|
|
48
|
+
export function parseRevisionArg(raw) {
|
|
49
|
+
if (!/^\d+$/.test(raw)) {
|
|
50
|
+
throw new CalendarDirectiveArgError(`--revision must be a non-negative integer (got ${JSON.stringify(raw)})`);
|
|
51
|
+
}
|
|
52
|
+
const value = Number(raw);
|
|
53
|
+
if (!Number.isSafeInteger(value)) {
|
|
54
|
+
throw new CalendarDirectiveArgError(`--revision ${raw} exceeds the safe integer range; it cannot be compared reliably`);
|
|
55
|
+
}
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* `calendar patch` 的 body 是不是在动 `deliveryMode` —— 是就必须过 `--apply` 门。
|
|
60
|
+
*
|
|
61
|
+
* 只看「键在不在」而不看值:`{"deliveryMode": null}` / 写错的枚举值同样是一次
|
|
62
|
+
* 「意图切换执行主体」的调用,服务端会拒,但它该在拒之前先被护栏拦下。
|
|
63
|
+
*/
|
|
64
|
+
export function deliveryModeChangeRequiresApply(body) {
|
|
65
|
+
return Object.prototype.hasOwnProperty.call(body, "deliveryMode");
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* `calendar patch` 要不要进 `assertApplyOrDryRun`。
|
|
69
|
+
*
|
|
70
|
+
* 两个判据故意不同:
|
|
71
|
+
* - `--dry-run` **对任何 body 都生效** —— 全局 flag 契约是「预览写入,不调用 API」。
|
|
72
|
+
* 只在 deliveryMode 分支上认它,就会出现「加了 --dry-run 却真的写了」的意外写入。
|
|
73
|
+
* - `--apply` 只在 body 含 `deliveryMode` 时才是必填(那是 D1 闸门);其余字段
|
|
74
|
+
* (status / permalink / …)维持既有的无门行为,不破坏存量自动化。
|
|
75
|
+
*/
|
|
76
|
+
export function calendarPatchNeedsGate(body, opts) {
|
|
77
|
+
return Boolean(opts.dryRun) || deliveryModeChangeRequiresApply(body);
|
|
78
|
+
}
|
|
79
|
+
export function registerCalendarDirectiveCommands(calendar) {
|
|
80
|
+
calendar
|
|
81
|
+
.command("preflight")
|
|
82
|
+
.description("GET /publishing-calendar-directives/:entryId/preflight —— 外部 agent 发帖前的只读校验(拿到 decision=ok 才能发)")
|
|
83
|
+
.option("-t, --team <teamId>", "Team UUID(缺省用当前 context)")
|
|
84
|
+
.requiredOption("-e, --entry <entryId>", "Calendar entry UUID")
|
|
85
|
+
.requiredOption("--revision <n>", "你手上那条 directive 的 revision(必填,不能猜:0 是合法初始值)")
|
|
86
|
+
.option("--require-ok", "decision 不是 ok 时以退出码 1 结束(默认恒 0,业务拒绝不是错误)", false)
|
|
87
|
+
.action(async (opts) => {
|
|
88
|
+
let revision;
|
|
89
|
+
try {
|
|
90
|
+
revision = parseRevisionArg(String(opts.revision));
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
console.error(e instanceof Error ? e.message : "Invalid --revision");
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
const res = await requireClient().preflightPublishingCalendarDirective(resolveTeamId(opts.team), opts.entry, revision);
|
|
97
|
+
// 无论放行还是拒绝,都先把完整判定写出去:`--require-ok` 的调用方也需要
|
|
98
|
+
// 那个 `reason` 才知道该删 cron 还是该等新指令。
|
|
99
|
+
console.log(JSON.stringify(res, null, 2));
|
|
100
|
+
if (opts.requireOk && res.decision !== "ok") {
|
|
101
|
+
// `process.exitCode` 而不是 `process.exit()`:后者会在 stdout 还没 flush
|
|
102
|
+
// 完时把进程掐掉,管道下游可能只拿到半截 JSON。
|
|
103
|
+
process.exitCode = 1;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
calendar
|
|
107
|
+
.command("set-delivery-mode")
|
|
108
|
+
.description("PATCH /calendar-entries/:id —— 切换这条条目由谁发布(hub_managed = Hub 内部 job;external_agent = 外部 agent 收指令建 cron)")
|
|
109
|
+
.option("-t, --team <teamId>", "Team UUID(缺省用当前 context)")
|
|
110
|
+
.requiredOption("-e, --entry <entryId>", "Calendar entry UUID")
|
|
111
|
+
.requiredOption("--mode <mode>", `${DELIVERY_MODES.join(" | ")}(防重复发帖的唯一闸门)`)
|
|
112
|
+
.option("--dry-run", "预览", false)
|
|
113
|
+
.option("--apply", "执行写入", false)
|
|
114
|
+
.action(async (opts) => {
|
|
115
|
+
if (!isDeliveryMode(opts.mode)) {
|
|
116
|
+
console.error(`Invalid --mode ${JSON.stringify(String(opts.mode))}; expected one of ${DELIVERY_MODES.join(", ")}`);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
const teamId = resolveTeamId(opts.team);
|
|
120
|
+
if (!assertApplyOrDryRun(opts, {
|
|
121
|
+
command: "calendar set-delivery-mode",
|
|
122
|
+
danger: "切换发布主体:转 external_agent 会撤销内部 publish_post job 并发 upsert 指令;转 hub_managed 会发 cancel 指令并建内部 job。改错 = 双发或谁都不发。",
|
|
123
|
+
summary: {
|
|
124
|
+
teamId,
|
|
125
|
+
calendarEntryId: opts.entry,
|
|
126
|
+
deliveryMode: opts.mode,
|
|
127
|
+
},
|
|
128
|
+
})) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const res = await requireClient().updateCalendarEntry(teamId, opts.entry, { deliveryMode: opts.mode });
|
|
132
|
+
console.log(JSON.stringify(res, null, 2));
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=register-calendar-directives.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register-calendar-directives.js","sourceRoot":"","sources":["../src/register-calendar-directives.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register-calendar-directives.test.d.ts","sourceRoot":"","sources":["../src/register-calendar-directives.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { CalendarDirectiveArgError, calendarPatchNeedsGate, deliveryModeChangeRequiresApply, isDeliveryMode, parseRevisionArg, registerCalendarDirectiveCommands, } from "./register-calendar-directives.js";
|
|
4
|
+
/**
|
|
5
|
+
* revision 是 preflight 的版本钉子:它一旦被「默认值化」,服务端就会拿一条真实
|
|
6
|
+
* 存在的 revision 0 条目放行内容。所以这里锁的不是「解析对不对」,而是
|
|
7
|
+
* **哪些输入必须拒绝**。
|
|
8
|
+
*/
|
|
9
|
+
describe("parseRevisionArg", () => {
|
|
10
|
+
it("接受 0 与普通非负整数", () => {
|
|
11
|
+
expect(parseRevisionArg("0")).toBe(0);
|
|
12
|
+
expect(parseRevisionArg("1")).toBe(1);
|
|
13
|
+
expect(parseRevisionArg("42")).toBe(42);
|
|
14
|
+
});
|
|
15
|
+
/**
|
|
16
|
+
* 🔴 这条是整个命令最重要的断言。`Number("")` 和 `Number(" ")` 都是 `0`,
|
|
17
|
+
* 而 0 是每条日历条目的合法初始 revision —— 用 `Number()` 兜底解析等于把
|
|
18
|
+
* 「忘了带版本号」翻译成「我手上是第 0 版」,服务端会照常放行内容。
|
|
19
|
+
*/
|
|
20
|
+
it("空串 / 空白 / 纯符号一律拒绝(绝不落到 0)", () => {
|
|
21
|
+
for (const raw of ["", " ", "\t", "+", "-"]) {
|
|
22
|
+
expect(() => parseRevisionArg(raw)).toThrow(CalendarDirectiveArgError);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
it("负数、小数、科学计数法、带空格的数字都拒绝", () => {
|
|
26
|
+
for (const raw of ["-1", "1.5", "1e3", " 1", "1 ", "0x10", "NaN"]) {
|
|
27
|
+
expect(() => parseRevisionArg(raw)).toThrow(CalendarDirectiveArgError);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
/**
|
|
31
|
+
* 纯数字但超出安全整数区:能过正则,转成 number 后值已经悄悄变了,
|
|
32
|
+
* 拿去比对的是另一条 revision。服务端也有 MAX_SAFE_INTEGER 上界。
|
|
33
|
+
*/
|
|
34
|
+
it("超出安全整数范围的纯数字串拒绝", () => {
|
|
35
|
+
expect(parseRevisionArg(String(Number.MAX_SAFE_INTEGER))).toBe(Number.MAX_SAFE_INTEGER);
|
|
36
|
+
expect(() => parseRevisionArg("9007199254740993")).toThrow(/safe integer range/);
|
|
37
|
+
expect(() => parseRevisionArg("99999999999999999999")).toThrow(CalendarDirectiveArgError);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
describe("isDeliveryMode", () => {
|
|
41
|
+
it("只认契约里的两个值", () => {
|
|
42
|
+
expect(isDeliveryMode("hub_managed")).toBe(true);
|
|
43
|
+
expect(isDeliveryMode("external_agent")).toBe(true);
|
|
44
|
+
for (const bad of ["", "HUB_MANAGED", "external", null, undefined, 1]) {
|
|
45
|
+
expect(isDeliveryMode(bad)).toBe(false);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
/**
|
|
50
|
+
* `calendar set-delivery-mode` 带 `--apply` 门,但 `calendar patch --body` 能写
|
|
51
|
+
* 任意字段。若 raw patch 不同样挡住 `deliveryMode`,那道门就只是装饰。
|
|
52
|
+
*/
|
|
53
|
+
describe("deliveryModeChangeRequiresApply", () => {
|
|
54
|
+
it("body 里出现 deliveryMode 就要门", () => {
|
|
55
|
+
expect(deliveryModeChangeRequiresApply({ deliveryMode: "external_agent" })).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
/** 只看键在不在:写错的值/显式 null 同样是一次「意图切换执行主体」的调用。 */
|
|
58
|
+
it("值非法或为 null 也要门(只看键存在与否)", () => {
|
|
59
|
+
expect(deliveryModeChangeRequiresApply({ deliveryMode: null })).toBe(true);
|
|
60
|
+
expect(deliveryModeChangeRequiresApply({ deliveryMode: "nope" })).toBe(true);
|
|
61
|
+
expect(deliveryModeChangeRequiresApply({ deliveryMode: undefined })).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
/** 既有用法(改状态/回填 permalink)行为必须完全不变,不能被新门牵连。 */
|
|
64
|
+
it("不含 deliveryMode 的既有 body 不要门", () => {
|
|
65
|
+
expect(deliveryModeChangeRequiresApply({
|
|
66
|
+
status: "succeeded",
|
|
67
|
+
permalink: "https://www.reddit.com/r/x/comments/y/",
|
|
68
|
+
})).toBe(false);
|
|
69
|
+
expect(deliveryModeChangeRequiresApply({})).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
describe("calendarPatchNeedsGate", () => {
|
|
73
|
+
/**
|
|
74
|
+
* 🔴 `--dry-run` 的全局契约是「预览写入,不调用 API」。若只在 deliveryMode 分支上
|
|
75
|
+
* 认它,`--dry-run --body '{"status":"succeeded"}'` 会**真的把状态改掉** ——
|
|
76
|
+
* 这是最不该有的一类意外写入。
|
|
77
|
+
*/
|
|
78
|
+
it("--dry-run 对任何 body 都要进门", () => {
|
|
79
|
+
expect(calendarPatchNeedsGate({ status: "succeeded" }, { dryRun: true })).toBe(true);
|
|
80
|
+
expect(calendarPatchNeedsGate({}, { dryRun: true })).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
it("deliveryMode 变更即使没 --dry-run 也要进门(--apply 必填)", () => {
|
|
83
|
+
expect(calendarPatchNeedsGate({ deliveryMode: "external_agent" }, {})).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
/** 既有无门用法必须原样保留:不带 --dry-run 又不动 deliveryMode = 直接写。 */
|
|
86
|
+
it("既有 body + 无 --dry-run 时完全不进门", () => {
|
|
87
|
+
expect(calendarPatchNeedsGate({ status: "succeeded", permalink: "https://example.test/" }, { dryRun: false })).toBe(false);
|
|
88
|
+
expect(calendarPatchNeedsGate({ status: "failed" }, {})).toBe(false);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
describe("registerCalendarDirectiveCommands", () => {
|
|
92
|
+
function build() {
|
|
93
|
+
const calendar = new Command("calendar");
|
|
94
|
+
calendar.exitOverride();
|
|
95
|
+
registerCalendarDirectiveCommands(calendar);
|
|
96
|
+
return calendar;
|
|
97
|
+
}
|
|
98
|
+
it("把两个子命令挂到 calendar 组下", () => {
|
|
99
|
+
expect(build()
|
|
100
|
+
.commands.map((c) => c.name())
|
|
101
|
+
.sort()).toEqual(["preflight", "set-delivery-mode"]);
|
|
102
|
+
});
|
|
103
|
+
/**
|
|
104
|
+
* `--revision <n>` 必须带值占位符:写成 `--revision` 会被 commander 当成布尔
|
|
105
|
+
* 开关,`opts.revision` 恒为 `true`,于是 `String(true)` 落进解析器被拒 ——
|
|
106
|
+
* 表现为「传了版本号却说非法」,极难排查。
|
|
107
|
+
*/
|
|
108
|
+
it("--revision 是必填且带值,--require-ok 是布尔开关", () => {
|
|
109
|
+
const preflight = build().commands.find((c) => c.name() === "preflight");
|
|
110
|
+
const options = preflight?.options ?? [];
|
|
111
|
+
const revision = options.find((o) => o.long === "--revision");
|
|
112
|
+
expect(revision?.required).toBe(true);
|
|
113
|
+
expect(revision?.mandatory).toBe(true);
|
|
114
|
+
const requireOk = options.find((o) => o.long === "--require-ok");
|
|
115
|
+
expect(requireOk?.required).toBe(false);
|
|
116
|
+
expect(requireOk?.mandatory).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
/** 切发布主体是高风险写:两个门选项必须都在。 */
|
|
119
|
+
it("set-delivery-mode 带 --apply / --dry-run 门", () => {
|
|
120
|
+
const cmd = build().commands.find((c) => c.name() === "set-delivery-mode");
|
|
121
|
+
const longs = (cmd?.options ?? []).map((o) => o.long);
|
|
122
|
+
expect(longs).toContain("--apply");
|
|
123
|
+
expect(longs).toContain("--dry-run");
|
|
124
|
+
expect((cmd?.options ?? []).find((o) => o.long === "--mode")?.mandatory).toBe(true);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
//# sourceMappingURL=register-calendar-directives.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register-calendar-directives.test.js","sourceRoot":"","sources":["../src/register-calendar-directives.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrape-output.test.d.ts","sourceRoot":"","sources":["../src/scrape-output.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
vi.mock("@geoly-ai/social-hub-sdk", () => ({
|
|
3
|
+
SocialHubClient: vi.fn().mockImplementation(() => ({})),
|
|
4
|
+
getSocialHubHealth: vi.fn(),
|
|
5
|
+
}));
|
|
6
|
+
const { formatScrapeFailure, formatScrapeError } = await import("./index.js");
|
|
7
|
+
describe("scrape CLI 输出", () => {
|
|
8
|
+
it("业务失败摘要带 outcome / 阶段 / request id / 证据", () => {
|
|
9
|
+
const line = formatScrapeFailure({
|
|
10
|
+
outcome: "login_wall",
|
|
11
|
+
finalUrl: "https://x.com/i/flow/login",
|
|
12
|
+
targetStatusCode: 200,
|
|
13
|
+
failureStage: null,
|
|
14
|
+
providerRequestId: "fc-req-9",
|
|
15
|
+
requestId: "req-1",
|
|
16
|
+
attempts: 1,
|
|
17
|
+
retryAfterMs: null,
|
|
18
|
+
classifierVersion: "scrape-classify-v1",
|
|
19
|
+
classificationEvidence: [
|
|
20
|
+
{ ruleId: "login-final-url-path", signal: "login-path" },
|
|
21
|
+
],
|
|
22
|
+
});
|
|
23
|
+
expect(line).toContain("outcome=login_wall");
|
|
24
|
+
expect(line).toContain("targetStatus=200");
|
|
25
|
+
expect(line).toContain("providerRequestId=fc-req-9");
|
|
26
|
+
expect(line).toContain("classifier=scrape-classify-v1");
|
|
27
|
+
expect(line).toContain("login-final-url-path(login-path)");
|
|
28
|
+
});
|
|
29
|
+
it("技术失败摘要把 provider typed error 挖出来,不再是通用错误", () => {
|
|
30
|
+
const line = formatScrapeError({
|
|
31
|
+
message: "HTTP 502: scrape service HTTP 500 [XHS_RENDER_FAILED]",
|
|
32
|
+
status: 502,
|
|
33
|
+
code: "SCRAPE_UPSTREAM_ERROR",
|
|
34
|
+
details: {
|
|
35
|
+
outcome: "upstream_error",
|
|
36
|
+
diagnostics: {
|
|
37
|
+
provider: {
|
|
38
|
+
providerStatusCode: 500,
|
|
39
|
+
providerErrorCode: "XHS_RENDER_FAILED",
|
|
40
|
+
providerRequestId: "fc-req-1",
|
|
41
|
+
providerScrapeId: "fc-scrape-2",
|
|
42
|
+
},
|
|
43
|
+
execution: { failureStage: "render", attemptCount: 2 },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
expect(line).toContain("code=SCRAPE_UPSTREAM_ERROR");
|
|
48
|
+
expect(line).toContain("outcome=upstream_error");
|
|
49
|
+
expect(line).toContain("stage=render");
|
|
50
|
+
expect(line).toContain("providerErrorCode=XHS_RENDER_FAILED");
|
|
51
|
+
expect(line).toContain("providerScrapeId=fc-scrape-2");
|
|
52
|
+
expect(line).toContain("attempts=2");
|
|
53
|
+
});
|
|
54
|
+
it("没有结构化 details 时退回原始 message(不编造字段)", () => {
|
|
55
|
+
expect(formatScrapeError(new Error("boom"))).toBe("boom");
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
//# sourceMappingURL=scrape-output.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrape-output.test.js","sourceRoot":"","sources":["../src/scrape-output.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAElD,EAAE,CAAC,IAAI,CAAC,0BAA0B,EAAE,GAAG,EAAE,CAAC,CAAC;IACzC,eAAe,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;IACvD,kBAAkB,EAAE,EAAE,CAAC,EAAE,EAAE;CAC5B,CAAC,CAAC,CAAC;AAEJ,MAAM,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;AAE9E,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,IAAI,GAAG,mBAAmB,CAAC;YAC/B,OAAO,EAAE,YAAY;YACrB,QAAQ,EAAE,4BAA4B;YACtC,gBAAgB,EAAE,GAAG;YACrB,YAAY,EAAE,IAAI;YAClB,iBAAiB,EAAE,UAAU;YAC7B,SAAS,EAAE,OAAO;YAClB,QAAQ,EAAE,CAAC;YACX,YAAY,EAAE,IAAI;YAClB,iBAAiB,EAAE,oBAAoB;YACvC,sBAAsB,EAAE;gBACtB,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,EAAE,YAAY,EAAE;aACzD;SACF,CAAC,CAAC;QACH,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;QAC7C,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAC;QAC3C,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,4BAA4B,CAAC,CAAC;QACrD,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,+BAA+B,CAAC,CAAC;QACxD,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,kCAAkC,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,IAAI,GAAG,iBAAiB,CAAC;YAC7B,OAAO,EAAE,uDAAuD;YAChE,MAAM,EAAE,GAAG;YACX,IAAI,EAAE,uBAAuB;YAC7B,OAAO,EAAE;gBACP,OAAO,EAAE,gBAAgB;gBACzB,WAAW,EAAE;oBACX,QAAQ,EAAE;wBACR,kBAAkB,EAAE,GAAG;wBACvB,iBAAiB,EAAE,mBAAmB;wBACtC,iBAAiB,EAAE,UAAU;wBAC7B,gBAAgB,EAAE,aAAa;qBAChC;oBACD,SAAS,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,EAAE;iBACvD;aACF;SACF,CAAC,CAAC;QACH,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,4BAA4B,CAAC,CAAC;QACrD,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,wBAAwB,CAAC,CAAC;QACjD,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;QACvC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,qCAAqC,CAAC,CAAC;QAC9D,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,8BAA8B,CAAC,CAAC;QACvD,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,CAAC,iBAAiB,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geoly-ai/social-hub-cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.20",
|
|
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.
|
|
26
|
+
"@geoly-ai/social-hub-sdk": "0.0.57"
|
|
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
|
|
package/skills/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
3
|
"package": "social-ops-hub",
|
|
4
|
-
"cliVersion": "0.3.
|
|
4
|
+
"cliVersion": "0.3.20",
|
|
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
|