Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions .github/workflows/generate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: AI Generate Post

# Generates a blog post from memory/ via the selected AI provider, then opens
# a Pull Request against develop for human review. Nothing is published
# directly — merging the PR (and later develop -> main) does the publishing.
#
# Triggers:
# - daily on a schedule
# - manually via the Actions tab
#
# Secrets (optional): ANTHROPIC_API_KEY or OPENAI_API_KEY. If neither is set,
# the generator runs its dry-run stub and still opens a PR for review.

on:
schedule:
# 22:43 UTC daily (~06:43 Asia/Shanghai) — before the publish workflow.
- cron: "43 22 * * *"
workflow_dispatch:
inputs:
provider:
description: "Provider override (anthropic | openai | dryrun)"
required: false
default: ""

permissions:
contents: write
pull-requests: write

jobs:
generate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: develop
Comment on lines +33 to +35

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find and read the workflow file
find . -name "generate.yml" -o -name "generate.yaml" | head -20

Repository: xiami303/OpenBlog

Length of output: 94


🏁 Script executed:

# Also check the repository structure for workflow files
git ls-files .github/workflows/ 2>/dev/null | head -20

Repository: xiami303/OpenBlog

Length of output: 122


🏁 Script executed:

# Read the full generate.yml workflow file
cat -n .github/workflows/generate.yml

Repository: xiami303/OpenBlog

Length of output: 2999


🌐 Web query:

actions/checkout persist-credentials default behavior GitHub Actions

💡 Result:

The default behavior of the persist-credentials input in the actions/checkout GitHub Action is true [1][2][3]. When set to true (the default), the action automatically configures the local Git repository with the provided GitHub token or SSH key [1][4][3]. This allows your workflow scripts to execute authenticated Git commands (such as git fetch or git push) without requiring manual credential configuration [1][5][6]. For security, the action now stores these credentials in a separate file under $RUNNER_TEMP rather than directly within the.git/config file, and the token is automatically removed during post-job cleanup [1][6]. If you do not want these credentials persisted in the Git configuration, you can opt out by setting persist-credentials to false in your workflow file [1][5][7].

Citations:


🌐 Web query:

peter-evans/create-pull-request credentials persist-credentials security

💡 Result:

When using the peter-evans/create-pull-request action in workflows that also use actions/checkout, you may encounter an HTTP 400 error described as a "Duplicate header: Authorization" [1][2][3]. This occurs because actions/checkout (specifically from version 6 onwards) persists a git credential by default, while peter-evans/create-pull-request also injects its own authentication header [4][2][3]. When Git attempts to push changes, both headers are sent, leading GitHub to reject the request [1][2][3]. To resolve this, the recommended approach is to set persist-credentials to false in your actions/checkout step [1][2][3]. This prevents the checkout action from storing credentials, leaving the peter-evans/create-pull-request action to handle the necessary authentication for the subsequent push [1][2]. Example workflow configuration: - uses: actions/checkout@v6 with: persist-credentials: false - name: Create Pull Request uses: peter-evans/create-pull-request@v8 with: token: ${{ secrets.GITHUB_TOKEN }} While the peter-evans/create-pull-request action has implemented internal measures to attempt to mitigate compatibility issues with actions/checkout@v6 (such as temporarily hiding credential files) [4][5][6], setting persist-credentials: false remains a common and effective practice to ensure reliable authentication and avoid duplicate header conflicts [1][2][3].

Citations:


Disable credential persistence on checkout.

actions/checkout@v4 keeps credentials enabled by default, persisting the GitHub token in Git configuration for subsequent steps. Since this workflow uses peter-evans/create-pull-request@v6 which manages its own authentication, set persist-credentials: false to narrow the credential blast radius and avoid authentication conflicts.

Suggested fix
      - uses: actions/checkout@v4
        with:
          ref: develop
+         persist-credentials: false
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 33-35: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 33-33: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/generate.yml around lines 33 - 35, The actions/checkout@v4
action in the workflow is configured without disabling credential persistence,
which leaves the GitHub token in Git configuration for subsequent steps and can
conflict with the downstream peter-evans/create-pull-request@v6 action that
manages its own authentication. Add the persist-credentials: false parameter to
the with section of the actions/checkout@v4 action to disable this default
behavior and reduce the credential blast radius.

Sources: MCP tools, Linters/SAST tools


- uses: actions/setup-node@v4
Comment on lines +33 to +37

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/generate.yml

Repository: xiami303/OpenBlog

Length of output: 2999


Pin the third-party actions to commit SHAs.

actions/checkout@v4 (line 33), actions/setup-node@v4 (line 37), and peter-evans/create-pull-request@v6 (line 61) use mutable tags. GitHub recommends pinning Actions to full-length commit SHAs to reduce supply-chain risk and ensure workflow reproducibility. (docs.github.com)

🧰 Tools
🪛 zizmor (1.25.2)

[warning] 33-35: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 33-33: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 37-37: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/generate.yml around lines 33 - 37, Replace the mutable
version tags with full-length commit SHAs for the three third-party GitHub
Actions: actions/checkout@v4, actions/setup-node@v4, and
peter-evans/create-pull-request@v6. For each action, look up the specific commit
SHA for the version being used and replace the tag (e.g., `@v4` or `@v6`) with the
complete commit hash in the format @<full-commit-sha>. This pins the actions to
immutable commit references, improving supply-chain security and ensuring
workflow reproducibility.

Sources: MCP tools, Linters/SAST tools

with:
node-version: "20"
cache: "npm"

- name: Install dependencies
run: npm ci

- name: Install AI SDK (if a key is configured)
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
if [ -n "$ANTHROPIC_API_KEY" ]; then npm install @anthropic-ai/sdk; fi
if [ -n "$OPENAI_API_KEY" ]; then npm install openai; fi

- name: Generate post
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENBLOG_PROVIDER: ${{ github.event.inputs.provider }}
run: node scripts/generate.mjs --date "$(date -u +%F)"

- name: Open pull request
uses: peter-evans/create-pull-request@v6
with:
base: develop
branch: ai/post-${{ github.run_id }}
add-paths: posts/**
commit-message: "AI: new post from memory ($(date -u +%F))"
title: "AI draft: new post from memory ($(date -u +%F))"
Comment on lines +66 to +67

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/generate.yml | head -80 | tail -20

Repository: xiami303/OpenBlog

Length of output: 1059


🌐 Web query:

peter-evans/create-pull-request action shell substitution commit-message title parameter evaluation

💡 Result:

The peter-evans/create-pull-request GitHub action does not perform internal shell substitution on its inputs [1][2]. Instead, it relies on the standard GitHub Actions workflow syntax for processing values before they are passed to the action [1]. To use variables, environment values, or command outputs within the title or commit-message parameters, you must use the ${{} } expression syntax directly in your workflow YAML file [1][3]. Because GitHub Actions evaluates these expressions before the step executes, the resolved values are passed to the action as static strings [1]. For example, to include a dynamic value (such as a date or a git commit hash) in your title or commit-message, you should resolve the value in a preceding step or use workflow context variables: jobs: createPullRequest: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Generate dynamic value id: vars run: echo "my_date=$(date +%Y-%m-%d)" >> $GITHUB_OUTPUT - name: Create Pull Request uses: peter-evans/create-pull-request@v8 with: title: "Update for ${{ steps.vars.outputs.my_date }}" commit-message: "Update for ${{ steps.vars.outputs.my_date }}" In this pattern, the shell substitution occurs in the run step, which then populates the GitHub Actions output variable. The create-pull-request action then receives the finalized string as the value for its inputs [1][2].

Citations:


Replace shell substitution with GitHub Actions expression or output variable.

$(date -u +%F) is shell syntax that only evaluates in run steps. The commit-message and title inputs to peter-evans/create-pull-request@v6 are not executed in a shell, so this will render literally. Use a GitHub Actions expression like ${{ github.run_id }} or generate the date in a preceding step and reference it via ${{ steps.step_id.outputs.variable_name }}.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/generate.yml around lines 66 - 67, The shell substitution
syntax $(date -u +%F) in the commit-message and title inputs to the
peter-evans/create-pull-request@v6 action will not be evaluated because these
inputs are not executed in a shell context and will render literally. Replace
the shell substitution with a GitHub Actions expression instead. You can either
use a built-in expression like ${{ github.run_id }} if that meets your needs, or
create a preceding step that generates the date using run with shell syntax,
assigns it to an output variable with id and outputs syntax, and then reference
that output in the commit-message and title inputs using ${{
steps.step_id.outputs.variable_name }} format.

Source: MCP tools

body: |
Auto-generated blog post from `memory/` notes, for review.

- Generated by the **AI Generate Post** workflow.
- Review the content, then merge into `develop`.
- Release happens separately via `develop` -> `main`.
labels: ai-generated
delete-branch: true
26 changes: 22 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,21 @@ Pages by CI.
.
├── AGENTS.md # this file
├── README.md # positioning + usage
├── package.json # scripts: build, serve
├── posts/ # Markdown articles (the content source)
├── package.json # scripts: build, serve, generate
├── memory/ # daily raw thoughts (input for AI generation)
│ └── *.md # one note per thought; front matter: date, tags
├── posts/ # Markdown articles (the site content source)
│ └── *.md # front matter: title, date, tags, summary
├── scripts/
│ └── build.mjs # static-site generator (Markdown -> public/)
│ ├── build.mjs # static-site generator (Markdown -> public/)
│ ├── serve.mjs # local preview server
│ ├── generate.mjs # AI article generator (memory/ -> posts/)
│ └── providers/ # pluggable AI providers (anthropic/openai/dryrun)
├── public/ # build output (git-ignored, regenerated)
└── .github/
└── workflows/
└── publish.yml # scheduled generate + publish to Pages
├── publish.yml # scheduled build + publish to Pages (from main)
└── generate.yml # scheduled AI generate -> PR to develop
```

## Branch policy — IMPORTANT
Expand All @@ -47,6 +53,7 @@ Toolchain: Node.js (ESM), deps `marked` + `gray-matter`.
npm ci # install (CI) — or npm install locally
npm run build # render posts/ -> public/
npm run serve # build, then serve public/ at http://localhost:8080
npm run generate -- --provider dryrun # memory/ -> a new posts/*.md (no API key needed)
```

Validation for a change: `npm run build` succeeds and produces
Expand All @@ -72,6 +79,17 @@ summary: One-line teaser shown on the index.
All fields are optional — missing title falls back to the first heading or the
filename; missing date falls back to file mtime.

## AI generation (memory/ -> posts/)

`scripts/generate.mjs` reads recent notes from `memory/`, asks the selected AI
provider to distill them into one article, and writes it to `posts/`. Provider
selection (`scripts/providers/index.mjs`): `OPENBLOG_PROVIDER` override, else
`ANTHROPIC_API_KEY` -> anthropic, else `OPENAI_API_KEY` -> openai, else
**dryrun** (a zero-dependency, no-network stub so the pipeline always runs). The
SDKs are `optionalDependencies` — install only when wiring a real key. The
`generate.yml` workflow runs this and opens a PR to `develop` for review; it
never publishes directly.

## Conventions

- Keep changes focused and minimal; match the style of surrounding files.
Expand Down
35 changes: 30 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ OpenBlog 用 AI Agent 把这条链路自动化:

- [x] 静态站点生成器(Markdown → HTML)
- [x] 自动构建与发布(GitHub Pages,定时 + 触发)
- [ ] 记忆采集与结构化存储
- [ ] AI 文章生成流水线(由记忆自动撰写)
- [x] AI 文章生成流水线(由记忆自动撰写,provider 可插拔)
- [ ] 记忆采集与结构化存储(目前为手动写入 `memory/`)
- [ ] 站点主题与个人化配置

## 快速开始
Expand Down Expand Up @@ -100,13 +100,38 @@ summary: 显示在首页的一句话简介。

所有字段都可省略 —— 没有 title 就用第一个标题或文件名,没有 date 就用文件修改时间。运行 `npm run build` 后,文章就会出现在站点上。

## 让 AI 由记忆自动写文章

把每天零散的所思所想写进 `memory/`(一篇 `.md` 一段想法),然后让 AI 把它们沉淀成一篇博客:

```bash
npm run generate # 读取近 7 天的 memory/ → 生成一篇文章到 posts/
npm run generate -- --days 3 --max 5 # 只看最近 3 天、最多 5 条记忆
npm run generate -- --provider dryrun # 强制用占位生成器(不调用 AI)
```

**Provider 可插拔,模型待定 —— 无需 key 也能跑:**

| 条件 | 使用的 provider |
| --- | --- |
| 设了 `ANTHROPIC_API_KEY` | `anthropic`(Claude,默认 `claude-opus-4-8`) |
| 设了 `OPENAI_API_KEY` | `openai` |
| 都没设 | `dryrun`(占位生成器,把记忆整理成文章骨架,**零依赖、不联网**) |

> 想接真实模型:设好对应的环境变量,再 `npm install @anthropic-ai/sdk`(或 `openai`)即可。可用 `OPENBLOG_PROVIDER` / `OPENBLOG_MODEL` 覆盖默认选择。

CI 里的 **AI Generate Post** 工作流会每日定时(或手动触发)运行生成器,并把结果**开成一个针对 `develop` 的 PR 供你审阅**;审阅合并后再走 `develop → main` 发布。API key 通过仓库 Secrets(`ANTHROPIC_API_KEY` / `OPENAI_API_KEY`)注入;没配也会用 dryrun 开 PR。

## 目录结构

```
posts/ # Markdown 文章(内容源)
scripts/build.mjs # 静态站点生成器
memory/ # 每日零散想法(AI 生成的输入源)
posts/ # Markdown 文章(站点内容)
scripts/build.mjs # 静态站点生成器(Markdown → public/)
scripts/generate.mjs # AI 文章生成器(memory/ → posts/)
scripts/providers/ # 可插拔的 AI provider(anthropic / openai / dryrun)
public/ # 构建产物(自动生成,已 gitignore)
.github/workflows/ # 定时生成 + 自动发布到 Pages
.github/workflows/ # 定时生成开 PR + 自动发布到 Pages
```

## 分支规则
Expand Down
Empty file added memory/.gitkeep
Empty file.
11 changes: 11 additions & 0 deletions memory/2026-06-12-example.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
date: 2026-06-12
tags: [思考, 工具]
---

# 今天的零散想法

- 读到一句话:「工具的价值不在于它能做什么,而在于它让你不必做什么。」一直在想,博客最该被自动化掉的不是「写」,而是「坚持写」这件事本身。
- 试着把一天里冒出来的念头随手记到这个 memory/ 目录,哪怕只是一句话。它们是原料,不是成品。
- 一个观察:碎片想法之所以容易丢,是因为它们没有「被处理」的下一步。如果记下来之后会自动被沉淀成一篇文章,记录的动力就完全不一样了。
- 待深入:记忆 → 文章 的转化,关键不是润色,而是「从一堆零散里找出那条主线」。
Loading