autocrew 0.3.6 → 0.3.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autocrew",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
4
4
  "description": "One-person content studio powered by AI — from trending topics to published posts",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -0,0 +1,39 @@
1
+ import type { CommandDef } from "./index.js";
2
+ import { getOption } from "./index.js";
3
+
4
+ export const cmd: CommandDef = {
5
+ name: "draft",
6
+ description: "Generate a content draft from a topic (loads style + methodology + wiki)",
7
+ usage: "autocrew draft <topic-title> [--platform <platform>]",
8
+ action: async (args, runner) => {
9
+ const topicTitle = args.filter((a) => !a.startsWith("--")).join(" ");
10
+ if (!topicTitle) {
11
+ console.error("Usage: autocrew draft <topic-title> [--platform <platform>]");
12
+ console.error("Example: autocrew draft 'vibe-coding 实践者的真实工作流' --platform xiaohongshu");
13
+ process.exitCode = 1;
14
+ return;
15
+ }
16
+
17
+ const platform = getOption(args, "--platform") || "xiaohongshu";
18
+
19
+ const result = await runner.execute("autocrew_content", {
20
+ action: "draft",
21
+ topic_title: topicTitle,
22
+ platform,
23
+ });
24
+
25
+ if (!result.ok) {
26
+ console.error(`Draft failed: ${result.error || "unknown error"}`);
27
+ process.exitCode = 1;
28
+ return;
29
+ }
30
+
31
+ console.log(`Draft context loaded for: "${topicTitle}" (${platform})`);
32
+ console.log(`\nCreator: ${(result.creatorContext as any)?.industry || "not configured"}`);
33
+ console.log(`Style: ${(result.style as string)?.slice(0, 80) || "no style file"}...`);
34
+ console.log(`Wiki context: ${(result.wikiContext as string)?.slice(0, 80) || "none"}...`);
35
+ console.log(`\nWriting instructions loaded (Operating System + Two-Phase Creation).`);
36
+ console.log(`\nNext step: Generate the draft body, then save with:`);
37
+ console.log(` autocrew_content action="save" title="..." body="..." platform="${platform}"`);
38
+ },
39
+ };
@@ -99,6 +99,7 @@ import { cmd as advanceCmd } from "./advance.js";
99
99
  import { cmd as trashCmd } from "./trash.js";
100
100
  import { cmd as restoreCmd } from "./restore.js";
101
101
  import { cmd as migrateCmd } from "./migrate.js";
102
+ import { cmd as draftCmd } from "./draft.js";
102
103
 
103
104
  export const commands: CommandDef[] = [
104
105
  statusCmd,
@@ -128,4 +129,5 @@ export const commands: CommandDef[] = [
128
129
  trashCmd,
129
130
  restoreCmd,
130
131
  migrateCmd,
132
+ draftCmd,
131
133
  ];
@@ -36,7 +36,26 @@ export const cmd: CommandDef = {
36
36
  return;
37
37
  }
38
38
 
39
- console.log(`Auto-fix complete for "${resolved.title}" (${resolved.source})`);
39
+ // Write fixed text back to source file
40
+ const fixedText = (result.autoFixedText || result.fixedText || "") as string;
41
+ if (fixedText && resolved.source.startsWith("file:")) {
42
+ const fs = await import("node:fs/promises");
43
+ const filePath = resolved.source.replace("file:", "");
44
+ await fs.writeFile(filePath, fixedText, "utf-8");
45
+ console.log(`Auto-fix complete for "${resolved.title}" — saved back to ${filePath}`);
46
+ } else if (fixedText && resolved.source.startsWith("pipeline:")) {
47
+ const fs = await import("node:fs/promises");
48
+ const { findProject } = await import("../../storage/pipeline-store.js");
49
+ const slug = resolved.source.replace("pipeline:", "");
50
+ const found = await findProject(slug);
51
+ if (found) {
52
+ const path = await import("node:path");
53
+ await fs.writeFile(path.join(found.dir, "draft.md"), fixedText, "utf-8");
54
+ console.log(`Auto-fix complete for "${resolved.title}" — saved back to draft.md`);
55
+ }
56
+ } else {
57
+ console.log(`Auto-fix complete for "${resolved.title}" (${resolved.source})`);
58
+ }
40
59
  console.log(` Sensitive words fixed: ${result.sensitiveWordsFixed || 0}`);
41
60
  console.log(` AI traces fixed: ${result.aiFixesApplied || 0}`);
42
61
  return;
@@ -55,9 +55,10 @@ function breakLongClauses(text: string): { text: string; count: number } {
55
55
  const chineseLength = (trimmed.match(/[\u4e00-\u9fff]/g) || []).length;
56
56
  if (chineseLength <= 40) return line;
57
57
 
58
- const replaced = line
59
- .replace(/,(?=[^,。!?]{10,})/, "。")
60
- .replace(/;(?=[^;。!?]{8,})/, "。");
58
+ // Use /g flag to apply ALL breaks in one pass (convergence guarantee)
59
+ let replaced = line;
60
+ replaced = replaced.replace(/,(?=[^,。!?]{10,})/g, "。");
61
+ replaced = replaced.replace(/;(?=[^;。!?]{8,})/g, "。");
61
62
  if (replaced !== line) {
62
63
  count += 1;
63
64
  return replaced;
@@ -108,20 +109,10 @@ function reduceWeOpenings(text: string): { text: string; count: number } {
108
109
  return { text: nextLines.join("\n"), count: changed };
109
110
  }
110
111
 
111
- function addRhythmPhraseIfNeeded(text: string): { text: string; count: number } {
112
- if (/说白了|你想啊|问题来了/.test(text)) {
113
- return { text, count: 0 };
114
- }
115
-
116
- const paragraphs = text.split(/\n{2,}/);
117
- if (paragraphs.length < 2) {
118
- return { text, count: 0 };
119
- }
120
-
121
- const next = [...paragraphs];
122
- next.splice(1, 0, "说白了,这件事拼的不是工具数量,而是表达和执行。");
123
- return { text: next.join("\n\n"), count: 1 };
124
- }
112
+ // addRhythmPhraseIfNeeded removed.
113
+ // Previously inserted a hardcoded sentence ("说白了,这件事拼的不是工具数量,而是表达和执行。")
114
+ // that was unrelated to the actual content. Humanizer should only do
115
+ // substitution and deletion — never insert new content.
125
116
 
126
117
  export function humanizeZh(options: HumanizeZhOptions): HumanizeZhResult {
127
118
  const originalText = options.text || "";
@@ -154,11 +145,7 @@ export function humanizeZh(options: HumanizeZhOptions): HumanizeZhResult {
154
145
  changes.push(`减少“我们”开头句子 × ${weOpenings.count}`);
155
146
  }
156
147
 
157
- const rhythm = addRhythmPhraseIfNeeded(humanizedText);
158
- if (rhythm.count > 0) {
159
- humanizedText = rhythm.text;
160
- changes.push("补入 1 处口语化节奏句");
161
- }
148
+ // No content insertion — humanizer only substitutes and deletes.
162
149
 
163
150
  humanizedText = normalizeWhitespace(humanizedText);
164
151
  return {
@@ -1,5 +1,10 @@
1
1
  import { Type } from "@sinclair/typebox";
2
- import { saveTopic, listTopics } from "../storage/local-store.js";
2
+ import { saveTopic as legacySaveTopic, listTopics as legacyListTopics } from "../storage/local-store.js";
3
+ import {
4
+ saveTopic as pipelineSaveTopic,
5
+ listTopics as pipelineListTopics,
6
+ type TopicCandidate,
7
+ } from "../storage/pipeline-store.js";
3
8
 
4
9
  /**
5
10
  * Core tool logic — platform-agnostic.
@@ -23,14 +28,22 @@ export async function executeTopicCreate(params: Record<string, unknown>) {
23
28
  const dataDir = (params._dataDir as string) || undefined;
24
29
 
25
30
  if (action === "list") {
26
- const topics = await listTopics(dataDir);
27
- if (topics.length === 0) {
31
+ // List from both stores, deduplicate by title
32
+ const legacyTopics = await legacyListTopics(dataDir);
33
+ const pipelineTopics = await pipelineListTopics(undefined, dataDir);
34
+
35
+ const combined = [
36
+ ...legacyTopics.map((t) => ({ ...t, _store: "legacy" })),
37
+ ...pipelineTopics.map((t) => ({ title: t.title, domain: t.domain, tags: t.tags || [], score: t.score, _store: "pipeline" })),
38
+ ];
39
+
40
+ if (combined.length === 0) {
28
41
  return { ok: true, message: "No topics yet.", topics: [] };
29
42
  }
30
- return { ok: true, topics };
43
+ return { ok: true, topics: combined };
31
44
  }
32
45
 
33
- // create
46
+ // create — write to BOTH stores for compatibility
34
47
  const title = params.title as string;
35
48
  const description = params.description as string;
36
49
  const tags = (params.tags as string[]) || [];
@@ -39,12 +52,29 @@ export async function executeTopicCreate(params: Record<string, unknown>) {
39
52
  return { ok: false, error: "title and description are required for create" };
40
53
  }
41
54
 
42
- const topic = await saveTopic({
55
+ // Save to legacy store (for backward compat)
56
+ const legacyTopic = await legacySaveTopic({
43
57
  title,
44
58
  description,
45
59
  tags,
46
60
  source: (params.source as string) || undefined,
47
61
  }, dataDir);
48
62
 
49
- return { ok: true, topic };
63
+ // Also save to pipeline store (so start command finds it)
64
+ const now = new Date().toISOString();
65
+ const pipelineTopic: TopicCandidate = {
66
+ title,
67
+ domain: tags[0] || "general",
68
+ score: { heat: 50, differentiation: 50, audienceFit: 50, overall: 50 },
69
+ formats: [],
70
+ suggestedPlatforms: [],
71
+ createdAt: now,
72
+ intelRefs: [],
73
+ angles: [description],
74
+ audienceResonance: "",
75
+ references: [],
76
+ };
77
+ await pipelineSaveTopic(pipelineTopic, dataDir);
78
+
79
+ return { ok: true, topic: legacyTopic, pipelineSynced: true };
50
80
  }