Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug-report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ body:
id: version
attributes:
label: Package version
placeholder: "@cynos-ai/engineer 0.28.0"
placeholder: "@cynos-ai/engineer x.y.z"
validations:
required: true
- type: input
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

All notable public changes to Cynos Engineer are documented here.

## 0.28.3

- Refresh the bundled `ui-design` methodology and reference library from upstream v1.3.0 while preserving the Cynos evidence contract.
- Remove references to demos that are not bundled in the npm package.
- Correct the Engineer configuration path and clarify `PROJECT.md` limits in both README languages.
- Use bilingual release-note categories and remove internal planning references from public comments.

## 0.28.2

- Document Node.js and pi prerequisites.
Expand Down
4 changes: 2 additions & 2 deletions README-zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,10 @@ Cynos 当前包含 12 个实践:
用户配置存放在:

```text
~/.pi/agent/cynos-config.json
~/.pi/agent/cynos-engineer.json
```

`/cynos-config` 命令可编辑常用设置:语言、onboard 模式、子代理超时、工作感知的 compaction。搜索 API key、视觉模型、浏览器选项由 `@cynos-ai/tools` 提供——请用 `/cynos-tools-config` 编辑。
`/cynos-config` 命令可编辑常用设置:语言、onboard 模式、子代理超时、`PROJECT.md` 注入限制、工作感知的 compaction。搜索 API key、视觉模型、浏览器选项由 `@cynos-ai/tools` 提供——请用 `/cynos-tools-config` 编辑。

## 文档与维护

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,10 @@ Project state lives in the target project:
User configuration lives in:

```text
~/.pi/agent/cynos-config.json
~/.pi/agent/cynos-engineer.json
```

The `/cynos-config` command edits common settings: language, onboard mode, subagent timeout, and work-aware compaction. Search API keys, vision model, and browser options live in `@cynos-ai/tools` — edit them via `/cynos-tools-config`.
The `/cynos-config` command edits common settings: language, onboard mode, subagent timeout, `PROJECT.md` injection limits, and work-aware compaction. Search API keys, vision model, and browser options live in `@cynos-ai/tools` — edit them via `/cynos-tools-config`.

## Documentation and maintenance

Expand Down
2 changes: 1 addition & 1 deletion THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ are retained alongside bundled content where applicable.
## `skills/ui-design/`

- Upstream project: [ConardLi/garden-skills](https://github.com/ConardLi/garden-skills)
- Upstream content: `web-design-engineer` skill, bundled version v1.2.2
- Upstream content: `web-design-engineer` skill, bundled version v1.3.0
- License: MIT
- Local notice: [`skills/ui-design/LICENSE`](./skills/ui-design/LICENSE)
- Source record: [`skills/ui-design/SOURCE.md`](./skills/ui-design/SOURCE.md)
Expand Down
5 changes: 2 additions & 3 deletions extensions/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,8 @@ export interface Checkpoint {
//
// Runs mid-work (injected via the prompt hook, computed from
// capturedToolResults). It never decides completion and never archives a
// work. Pilot period: concerns and checkpoints are two parallel sets kept
// consistent by hand; a later `condition` unification is planned (see
// docs/v2/cross-project-constraint-architecture-research-2026-07.md §3.3).
// work. Concerns and checkpoints are deliberately separate: concerns coach
// the next action, while checkpoints make the final completion decision.
// ============================================================

export type ConcernStatus = "active" | "drift" | "satisfied";
Expand Down
2 changes: 1 addition & 1 deletion extensions/infra/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as path from "node:path";
// ============================================================
// Centralized path management
//
// Project-level .cynos/ state files + user-level ~/.pi/agent/cynos-config.json config
// Project-level .cynos/ state files + user-level ~/.pi/agent/cynos-engineer.json config
// are all exported from here. Other modules always go through these functions.
// ============================================================

Expand Down
2 changes: 1 addition & 1 deletion extensions/infra/project-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export interface ProjectContext {
}

// best-effort read of PROJECT.md.
// Missing file, permission errors, or encoding issues do not block start — only return a warning (§25).
// Missing file, permission errors, or encoding issues do not block start; return a warning instead.
// Truncation is per-line (default 600 lines), overridable via config.projectMdMaxLines.
export async function readProjectMd(cwd: string): Promise<ProjectContext> {
const filePath = projectMdPath(cwd);
Expand Down
7 changes: 4 additions & 3 deletions extensions/infra/tools-config-reader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import { cynosToolsConfigPath, legacyCynosConfigPath } from "./paths";
//
// Fallback chain:
// 1. ~/.pi/agent/cynos-tools.json -> visionModel
// 2. ~/.pi/agent/cynos-config.json -> visionModel (legacy, kept for one or two versions)
// 2. ~/.pi/agent/cynos-config.json -> visionModel (legacy fallback)
//
// This keeps looker working for users who have not re-run /cynos-tools-config since
// the split, without Engineer reaching into Tools internals.
// The fallback keeps looker working for users upgrading from a pre-split release
// who have not yet run /cynos-tools-config, without Engineer reaching into Tools
// internals. Remove it only in a planned breaking change after a deprecation period.

export async function readToolsVisionModel(): Promise<string | undefined> {
const tools = await readJsonFileOptional<{ visionModel?: unknown }>(cynosToolsConfigPath());
Expand Down
2 changes: 1 addition & 1 deletion extensions/practices/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1010,7 +1010,7 @@ export function mentionsOriginalPromptAuthorization(summary: string): boolean {
// unknown agent -> exitCode:1 + isError. So a successful subagent call's input.agent is
// guaranteed to be exactly one of the 5 fixed names. No outputSummary fallback is needed or
// wanted (scanning it caused false positives, e.g. an explorer mentioning 'review' counted as
// reviewer). See principles §3.8 criterion C (gate trigger on deterministic field, not text scan).
// reviewer). Use the deterministic agent field rather than scanning result text.
export function isSubagentResult(result: CapturedToolResult, expectedAgent: "reviewer" | "challenger"): boolean {
if (result.toolName !== "cynos_subagent" || result.isError) return false;
const agent = stringAt(result.input.agent);
Expand Down
2 changes: 1 addition & 1 deletion extensions/subagent/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// Uses --mode json to capture structured event stream (message_end, tool_result_end),
// rather than --print plain text, to capture metadata like usage, stopReason.
//
// Permission boundary (§34): child processes flag themselves via PE_CHILD=1 env var;
// Permission boundary: child processes flag themselves via PE_CHILD=1 env var;
// main-agent-only tools (e.g. cynos_subagent itself, vision guard) are not registered in children.

import { spawn } from "node:child_process";
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cynos-ai/engineer",
"version": "0.28.2",
"version": "0.28.3",
"description": "Cynos — an autonomous AI engineering runtime with evidence-based completion verification.",
"keywords": [
"pi-package",
Expand Down
47 changes: 28 additions & 19 deletions scripts/generate-changelog.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -38,42 +38,51 @@ function commitsSince(tag) {
}

function groupFor(subject) {
if (/^(feat|feature)(\(.+\))?:/i.test(subject)) return "新增";
if (/^(fix|bugfix)(\(.+\))?:/i.test(subject)) return "修复";
if (/^docs(\(.+\))?:/i.test(subject)) return "文档";
if (/^test(s)?(\(.+\))?:/i.test(subject)) return "测试";
if (/^(build|ci|chore|refactor)(\(.+\))?:/i.test(subject)) return "工程";
return "其他";
if (/^(feat|feature)(\(.+\))?:/i.test(subject)) return "Added";
if (/^(fix|bugfix)(\(.+\))?:/i.test(subject)) return "Fixed";
if (/^docs(\(.+\))?:/i.test(subject)) return "Documentation";
if (/^test(s)?(\(.+\))?:/i.test(subject)) return "Tests";
if (/^(build|ci|chore|refactor)(\(.+\))?:/i.test(subject)) return "Engineering";
return "Other";
}

function cleanSubject(subject) {
return subject.replace(/^[a-z]+(\(.+\))?:\s*/i, "").trim();
}

// 发版元数据提交不算用户可见变更,不应进入 changelog:
// - "docs: update changelog [skip ci]"(旧 CI 自动提交)
// - "release vX.Y.Z"(新原子发版脚本)
// - 裸版本号 "0.1.1"(旧 `npm version` 风格)
// Release metadata commits are not user-visible changes and should not enter
// the changelog:
// - "docs: update changelog [skip ci]" (legacy CI commit)
// - "release vX.Y.Z" or "release: package X.Y.Z" (release scripts)
// - bare versions such as "0.1.1" (legacy `npm version` style)
function isReleaseMetadata(subject) {
return /update changelog/i.test(subject)
|| /^release\s+v?\d/i.test(subject)
|| /^release(?:\s+|:\s+)(?:[^\s]+\s+)?v?\d/i.test(subject)
|| /^\d+\.\d+\.\d+$/.test(subject);
}

function renderReleaseNotes(tag, commits) {
const lines = [`## v${version} - ${today}`, ""];
if (tag) lines.push(`范围:${tag}..HEAD`, "");
else lines.push("范围:项目首次发布以来的全部提交。", "");
if (tag) lines.push(`Range: ${tag}..HEAD / 范围:${tag}..HEAD`, "");
else lines.push("Range: all commits since the first public release / 范围:项目首次公开发布以来的全部提交。", "");

if (commits.length === 0) {
lines.push("- 无新的提交。");
lines.push("- No new commits. / 无新的提交。");
return `${lines.join("\n")}\n`;
}

for (const group of ["新增", "修复", "文档", "测试", "工程", "其他"]) {
const items = commits.filter((commit) => groupFor(commit.subject) === group);
const groups = [
["Added", "新增"],
["Fixed", "修复"],
["Documentation", "文档"],
["Tests", "测试"],
["Engineering", "工程"],
["Other", "其他"],
];
for (const [english, chinese] of groups) {
const items = commits.filter((commit) => groupFor(commit.subject) === english);
if (items.length === 0) continue;
lines.push(`### ${group}`, "");
lines.push(`### ${english} / ${chinese}`, "");
for (const item of items) {
lines.push(`- ${cleanSubject(item.subject)} (${item.hash}, ${item.author})`);
}
Expand Down Expand Up @@ -106,10 +115,10 @@ if (args.has("--release-notes")) {
const expected = mergeChangelog(entry);
const current = readFileSync(resolve(root, "CHANGELOG.md"), "utf8");
if (current !== expected) {
console.error("CHANGELOG.md 不是最新。请运行 npm run changelog");
console.error("CHANGELOG.md is out of date. Run npm run changelog.");
process.exit(1);
}
} else {
writeFileSync(resolve(root, "CHANGELOG.md"), mergeChangelog(entry), "utf8");
process.stdout.write("CHANGELOG.md 已更新。\n");
process.stdout.write("CHANGELOG.md updated.\n");
}
57 changes: 25 additions & 32 deletions scripts/release.mjs
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
#!/usr/bin/env node
// ============================================================
// 原子发版脚本
// Atomic local release helper.
//
// 一次调用完成:校验 → 版本 bump → 重生成 CHANGELOG → 单次提交 → 打 tag。
// 关键不变量:CHANGELOG 与版本号、tag 必须落在同一个 commit 里,
// tag 指向的提交必须已经包含正确的 CHANGELOG。
// One invocation verifies the tree, bumps version metadata, regenerates the
// changelog, creates one release commit, and creates the annotated tag. The tag
// must point at the commit containing the version and changelog.
//
// 这取代了旧的 `npm version` + CI 自动补 changelog 提交的两步流程——
// 旧流程会让 tag 指向「只有版本号、没有 CHANGELOG」的提交,
// 且 CI 的自动提交会与本地分叉。
// Usage:
// npm run release -- patch | minor | major | <x.y.z>
//
// 用法:
// npm run release -- patch # 0.1.1 → 0.1.2
// npm run release -- minor # 0.1.1 → 0.2.0
// npm run release -- major # 0.1.1 → 1.0.0
// npm run release -- 1.2.3 # 显式版本号
//
// 完成后手动推送:
// Push after review with:
// git push origin main --follow-tags
// ============================================================
import { execFileSync } from "node:child_process";
Expand All @@ -28,7 +21,7 @@ const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const kind = process.argv[2];

if (!kind) {
console.error("用法: npm run release -- patch | minor | major | <x.y.z>");
console.error("Usage: npm run release -- patch | minor | major | <x.y.z>");
process.exit(1);
}

Expand All @@ -53,49 +46,49 @@ function run(args) {
execFileSync(process.platform === "win32" ? "npm.cmd" : "npm", args, { cwd: root, stdio: "inherit" });
}

// 1. 工作树必须干净,避免把无关改动混进 release commit
// 1. The working tree must be clean so unrelated changes cannot enter the release commit.
const status = git(["status", "--porcelain"]);
if (status) {
console.error("工作树不干净,请先提交或 stash\n" + status);
console.error("Working tree is not clean. Commit or stash first:\n" + status);
process.exit(1);
}

// 2. 发布只从 main 分支发起;先同步远端引用,避免本地基于过期 main tag
// 2. Release only from main and refresh remote refs before creating a tag.
const branch = git(["branch", "--show-current"]);
if (branch !== "main") {
console.error(`当前分支是 ${branch || "detached HEAD"},请切到 main 后再发版。`);
console.error(`Release must start from main; current branch is ${branch || "detached HEAD"}.`);
process.exit(1);
}
console.log("→ git fetch origin main --tags …");
gitInherit(["fetch", "origin", "main", "--tags"]);
if (!gitOk(["merge-base", "--is-ancestor", "origin/main", "HEAD"])) {
console.error("本地 main 不包含 origin/main 的最新提交。请先 rebase/merge 远端 main,再发版。");
console.error("Local main does not contain origin/main. Rebase or merge the remote main before releasing.");
process.exit(1);
}

// 3. 读当前版本,计算下一版本。
// 3. Read the current version and calculate the next version.
const pkgPath = resolve(root, "package.json");
const lockPath = resolve(root, "package-lock.json");
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
const current = pkg.version;
const next = resolveVersion(current, kind);
if (!next) {
console.error(`无法解析目标版本,输入为: ${kind}(当前 ${current}`);
console.error(`Cannot resolve target version: ${kind} (current ${current}).`);
process.exit(1);
}
if (next === current) {
console.error(`目标版本与当前相同: ${current}`);
console.error(`Target version is unchanged: ${current}.`);
process.exit(1);
}
const tag = `v${next}`;
if (gitOk(["rev-parse", "--verify", `refs/tags/${tag}`])) {
console.error(`tag 已存在: ${tag}`);
console.error(`Tag already exists: ${tag}`);
process.exit(1);
}

console.log(`准备发版: ${current} → ${next}`);
console.log(`Preparing release: ${current} → ${next}`);

// 4. 先跑完整校验,失败则中止(不产生任何改动)。
// 4. Run the full verification before making any file changes.
console.log("→ npm run verify …");
run(["run", "verify"]);

Expand All @@ -104,18 +97,18 @@ pkg.version = next;
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
bumpLockfile(lockPath, next);

// 6. 重生成 CHANGELOG(此时新 tag 尚未创建,脚本以上一个可达 tag 为分界,
// 生成新版本段)。必须在提交前完成,让 CHANGELOG 进入同一个 commit
// 6. Regenerate CHANGELOG before creating the tag so the new section is part
// of the same commit.
console.log("→ npm run changelog …");
run(["run", "changelog"]);

// 7. 单次提交 + 打 tag
// 7. Create one release commit and tag it.
git(["add", "package.json", "package-lock.json", "CHANGELOG.md"]);
git(["commit", "-m", `release ${tag}`]);
git(["tag", "-a", tag, "-m", tag]);

console.log(`\n✓ 已发版 ${tag}(commit 含版本号 + CHANGELOG + tag`);
console.log("推送:");
console.log(`\n✓ Released ${tag} (version, CHANGELOG, and tag share one commit).`);
console.log("Push:");
console.log(" git push origin main --follow-tags");

function resolveVersion(currentSemver, input) {
Expand All @@ -133,7 +126,7 @@ function resolveVersion(currentSemver, input) {
function bumpLockfile(path, version) {
const lock = JSON.parse(readFileSync(path, "utf8"));
lock.version = version;
// lockfile v3:根 packages[""] 也记录版本。
// Lockfile v3 also records the version in the root packages[""] entry.
if (lock.packages && lock.packages[""]) lock.packages[""].version = version;
writeFileSync(path, JSON.stringify(lock, null, 2) + "\n", "utf8");
}
2 changes: 1 addition & 1 deletion skills/cynos/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Select the most specific applicable practice. Do not use `default` just because

Review boundary examples: “review/find problems/assess correctness/security/maintainability of this existing code/design/PR/diff” means `review`, even when the report is only in chat. Test boundary examples: “run smoke and tell me the result” means `test`; “run smoke and write docs/smoke-report.md” means `docs`; “found a bug, fix it” means `debug`/`develop` after the test verdict. Refactor boundary examples: “give me a refactor plan, do not change code” means no practice for chat-only advice, or docs for a persisted plan. Frontend routing examples: “input is too short / long email is hard to see / button is too small / mobile layout overflows” means `usability`; “button click does nothing / valid email cannot save / page throws an error” means `debug`; “add a clear-email button / add a new visible control or action / support a new validation rule / change API or data flow” means `develop` even when framed as usability; “redesign the page style / brand colors / design system” means `ui-design`. “Explain what this code does”, “summarize this file”, “teach me this concept”, or future-oriented advice with no audited judgment means no practice.

**Project-external config ≠ practice.** Editing files outside the current project — `~/.pi/...`, `~/.config/...`, `~/.gitconfig`, `~/.npmrc`, switching the default model, deleting an old provider/extension, managing the agent’s own settings — do these directly without `cynos_start_work`. If you wrap such a tweak in a practice anyway it may still complete, but you are adding unnecessary ceremony — prefer doing it directly. To change Cynos preferences (including `onboardMode`), use the `/cynos-config` menu — practices never write `~/.pi/agent/cynos-config.json`.
**Project-external config ≠ practice.** Editing files outside the current project — `~/.pi/...`, `~/.config/...`, `~/.gitconfig`, `~/.npmrc`, switching the default model, deleting an old provider/extension, managing the agent’s own settings — do these directly without `cynos_start_work`. If you wrap such a tweak in a practice anyway it may still complete, but you are adding unnecessary ceremony — prefer doing it directly. To change Cynos preferences (including `onboardMode`), use the `/cynos-config` menu — practices never write `~/.pi/agent/cynos-engineer.json`.

If the user only asks a generic question, chat-only advice, or follows up on a completed work without new exploration, audited judgment, file output, or modification, answer normally without starting project practice.

Expand Down
Loading
Loading