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
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
},
"metadata": {
"description": "Thinloop skills and continuity hooks for Issue-backed discovery, read-only project-status navigation, multi-Issue project DAG decomposition and execution, evidence-backed reengineering and delivery, and approved skill evolution.",
"version": "0.16.0"
"version": "0.16.1"
},
"plugins": [
{
"name": "thinloop",
"source": ".",
"description": "Discovery, Web UIUX, architecture, Next project-status navigation, Project DAG decomposition, Execute READY-wave orchestration, Reengineering gates, QuickDev delivery, maintenance, knowledge, and evolution skills with continuity checks; Project itself remains non-executing.",
"version": "0.16.0"
"version": "0.16.1"
}
]
}
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "thinloop",
"version": "0.16.0",
"version": "0.16.1",
"description": "Simplify complex development with Issue-backed discovery, risk-adaptive Web experience and architecture design, non-executing multi-Issue project decomposition into dependency DAGs, bounded READY-wave execution, read-only project-status navigation, project-scale refactoring and cross-stack reimplementation, autonomous single-Issue PR delivery, evidence-backed maintenance, concise experiential knowledge, and human-approved skill evolution.",
"author": {
"name": "mindcarver"
Expand Down
4 changes: 2 additions & 2 deletions .codebuddy-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
},
"metadata": {
"description": "Thinloop skills and continuity hooks for Issue-backed discovery, read-only project-status navigation, multi-Issue project DAG decomposition and execution, evidence-backed reengineering and delivery, and approved skill evolution.",
"version": "0.16.0"
"version": "0.16.1"
},
"plugins": [
{
"name": "thinloop",
"source": ".",
"description": "Discovery, Web UIUX, architecture, Next project-status navigation, Project DAG decomposition, Execute READY-wave orchestration, Reengineering gates, QuickDev delivery, maintenance, knowledge, and evolution skills with continuity checks; Project itself remains non-executing.",
"version": "0.16.0"
"version": "0.16.1"
}
]
}
2 changes: 1 addition & 1 deletion .codebuddy-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "thinloop",
"version": "0.16.0",
"version": "0.16.1",
"description": "Simplify complex development with Issue-backed discovery, risk-adaptive Web experience and architecture design, non-executing multi-Issue project decomposition into dependency DAGs, bounded READY-wave execution, read-only project-status navigation, project-scale refactoring and cross-stack reimplementation, autonomous single-Issue PR delivery, evidence-backed maintenance, concise experiential knowledge, and human-approved skill evolution.",
"author": {
"name": "mindcarver"
Expand Down
2 changes: 1 addition & 1 deletion .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "thinloop",
"version": "0.16.0",
"version": "0.16.1",
"description": "Simplify complex development with Issue-backed discovery, risk-adaptive Web experience and architecture design, non-executing multi-Issue project decomposition into dependency DAGs, bounded READY-wave execution, read-only project-status navigation, project-scale refactoring and cross-stack reimplementation, autonomous single-Issue PR delivery, evidence-backed maintenance, concise experiential knowledge, and human-approved skill evolution.",
"author": {
"name": "mindcarver"
Expand Down
24 changes: 22 additions & 2 deletions .dsh-plugin/continuity.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ function correctiveMessage(text) {
}

export function apply(ctx) {
// Per live Agent, without persisting task data or retaining finished agents.
const corrections = new WeakMap();
ctx.on("agent/turn-stopping", async ({ agent, signal }) => {
if (signal?.aborted) return;

Expand All @@ -50,7 +52,10 @@ export function apply(ctx) {
try {
markdown = await readFile(statePath, "utf8");
} catch (error) {
if (error?.code === "ENOENT") return; // no fallback state → nothing to protect
if (error?.code === "ENOENT") {
corrections.delete(agent);
return; // no fallback state → nothing to protect
}
ctx.logger.warn(
`thinloop continuity check failed open: ${
error instanceof Error ? error.message : String(error)
Expand All @@ -60,9 +65,24 @@ export function apply(ctx) {
}

const result = validateState(markdown);
if (!result.managed || result.issues.length === 0) return;
if (!result.managed || result.issues.length === 0) {
corrections.delete(agent);
return;
}

const previous = corrections.get(agent);
if (previous?.statePath === statePath && previous.markdown === markdown) {
ctx.logger.warn(
`thinloop unresolved continuity handoff: .scd/tasks/current.md ` +
`is unchanged after correction; no further steering. ` +
`The task is not complete. Resume by fixing the note:\n` +
result.issues.map((issue) => `- ${issue}`).join("\n"),
);
return;
}

const issueList = result.issues.map((issue) => `- ${issue}`).join("\n");
corrections.set(agent, { statePath, markdown });
agent.steer(
correctiveMessage(
`${result.managedBy} paused before stopping because ` +
Expand Down
2 changes: 1 addition & 1 deletion .zcode-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "thinloop",
"version": "0.16.0",
"version": "0.16.1",
"description": "Simplify complex development with Issue-backed discovery, risk-adaptive Web experience and architecture design, non-executing multi-Issue project decomposition into dependency DAGs, bounded READY-wave execution, read-only project-status navigation, project-scale refactoring and cross-stack reimplementation, autonomous single-Issue PR delivery, evidence-backed maintenance, concise experiential knowledge, and human-approved skill evolution.",
"author": {
"name": "mindcarver"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
</p>

<p align="center">
<kbd>v0.16.0</kbd>
<kbd>v0.16.1</kbd>
&nbsp;
<kbd>ISSUE-DRIVEN</kbd>
&nbsp;
Expand Down
5 changes: 5 additions & 0 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,11 @@ codebuddy plugin update thinloop@thinloop --scope user
- 升级到 v0.16.0:确认 `Thinloop CI`、当前三臂评测、单一路由内核、README 产品表面和
Tag-only GitHub Release 链路均存在;发布范围、证据和已知限制见
[`docs/releases/v0.16.0.md`](./releases/v0.16.0.md)。
- 升级到 v0.16.1:连续性检查兼容当前中文与旧英文状态,保留明确模板占位符检查,
允许 HTML、泛型与 Markdown 自动链接。Claude Stop 首次要求纠正,重复纠正仍失败时
输出未解决状态交接并结束自动纠正;DSH 对同一 Agent 的未改变错误状态不重复 steer,
状态改变或恢复后可重新纠正。更新后新建会话使新 Hook 生效,版本与验证范围见
[`docs/releases/v0.16.1.md`](./releases/v0.16.1.md)。WorkBuddy / ZCode 维持 SKIP。
- 若从 v0.6.x 升级,另确认旧 `scd-dev-loop` 已消失。

更新后可以在 Thinloop 源码仓库运行只读检查器:
Expand Down
15 changes: 15 additions & 0 deletions docs/releases/v0.16.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Thinloop v0.16.1

## 范围

- 连续性校验兼容当前中文任务模板和旧英文章节、边界;明确的当前与旧模板占位符及 TODO/TBD 仍必须填写,HTML、泛型和 Markdown 自动链接不再误拦。
- Claude Stop 第一次发现不可恢复状态时仍要求修复;`stop_hook_active: true` 时输出未解决状态交接并停止自动纠正,交接不会把任务标记为完成。PreCompact 和其他平台原有输出协议不变。
- DSH 为每个存活 Agent 记录最近一次已纠正状态;同一错误内容不再次 steer,状态改变、恢复或文件移除后允许后续纠正。记录只存在内存,不写入用户状态或数据库。
- 插件版本、市场清单、README 与升级入口统一为 0.16.1。

## 验证与边界

- 回归检查覆盖合法技术内容、正确填充与未填写的真实中文模板、旧英文状态、TODO/TBD、Claude Stop 首次与重复子进程输出、其他事件协议,以及 DSH 每 Agent 隔离和恢复。
- DSH 的适配器检查不等于真实模型会话验收;Claude Hook 子进程检查不等于宿主完整任务交付。
- 合并后刷新受影响的已安装 Agent,再核验版本、Skill 载荷和各平台支持的 Hook;WorkBuddy 与 ZCode 继续 `verification.mode: skip`。
- 本补丁不表示模型效果、完整交付评测或成本有改善;其他审计项的实现和验收由对应 Issue 单独记录。
18 changes: 18 additions & 0 deletions hooks/check-state.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,24 @@ async function main() {
}

const eventName = hookInput.hook_event_name || "lifecycle event";
if (
process.env.CLAUDE_PLUGIN_ROOT &&
!process.env.CODEBUDDY_PLUGIN_ROOT &&
!process.env.ZCODE_PLUGIN_ROOT &&
eventName === "Stop" &&
hookInput.stop_hook_active === true
) {
emit({
continue: true,
systemMessage:
`${result.managedBy} unresolved continuity handoff: ` +
`.scd/tasks/current.md is still not resumable after correction. ` +
`Stopping automatic correction; this task is not complete. ` +
`Resume by fixing the note before further delivery:\n` +
result.issues.map((issue) => `- ${issue}`).join("\n"),
});
return;
}
block(eventName, result.managedBy, result.issues);
} catch (error) {
emit({
Expand Down
36 changes: 31 additions & 5 deletions hooks/validate-state.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,30 @@ export const REQUIRED_SECTIONS = [
"Next action",
];

const SECTION_ALIASES = {
Outcome: "结果",
Boundaries: "边界",
Acceptance: "验收条件",
Decisions: "决策",
Evidence: "证据",
"Next action": "下一步行动",
};

// Current Chinese and previous English current-task templates. Arbitrary angle
// brackets also occur in HTML, type parameters and Markdown autolinks.
const TEMPLATE_MARKERS = [
"<GitHub Issue 地址>",
"<GitHub Issue URL>",
"<ISO-8601 timestamp with timezone>",
"<Issue 验收 ID 与当前状态>",
"<Issue acceptance ID and current state>",
"<命令或可观察行动>",
"<command or observable action>",
"<唯一且具体的行动>",
"<Exactly one concrete action>",
"<One observable result>",
];

export function parseFrontmatter(markdown) {
const match = markdown.match(/^---\s*\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
if (!match) {
Expand Down Expand Up @@ -61,9 +85,9 @@ export function sectionContent(markdown, heading) {

export function hasPlaceholder(content) {
return (
/<[^>\r\n]+>/.test(content) ||
TEMPLATE_MARKERS.some((marker) => content.includes(marker)) ||
/\[(?:TODO|TBD)\b[^\]]*\]/i.test(content) ||
/^\s*(?:TODO|TBD)\s*$/im.test(content)
/^\s*(?:[-*]\s+)?(?:TODO|TBD)(?:\s*[::].*)?\s*$/im.test(content)
);
}

Expand Down Expand Up @@ -102,7 +126,9 @@ export function validateState(markdown) {

const sections = {};
for (const heading of REQUIRED_SECTIONS) {
const content = sectionContent(markdown, heading);
const content =
sectionContent(markdown, heading) ??
sectionContent(markdown, SECTION_ALIASES[heading]);
sections[heading] = content;
if (content === null) {
issues.push(`missing section: ${heading}`);
Expand All @@ -115,8 +141,8 @@ export function validateState(markdown) {

if (
sections.Boundaries &&
(!/^\s*-\s*In\s*:/im.test(sections.Boundaries) ||
!/^\s*-\s*Out\s*:/im.test(sections.Boundaries))
(!/^\s*-\s*(?:In\s*:|范围内\s*[::])/im.test(sections.Boundaries) ||
!/^\s*-\s*(?:Out\s*:|范围外\s*[::])/im.test(sections.Boundaries))
) {
issues.push("Boundaries must include both In and Out entries");
}
Expand Down
2 changes: 1 addition & 1 deletion marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"name": "thinloop",
"source": ".",
"description": "Discovery, Web UIUX, architecture, Next project-status navigation, Project DAG decomposition, Execute READY-wave orchestration, Reengineering gates, QuickDev delivery, maintenance, knowledge, and evolution skills with continuity checks; Project itself remains non-executing.",
"version": "0.16.0"
"version": "0.16.1"
}
]
}
36 changes: 36 additions & 0 deletions tests/check-state.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -353,3 +353,39 @@ test("fails open with a warning on invalid hook input", () => {
fs.rmSync(cwd, { recursive: true, force: true });
}
});


test("Claude Stop offers one correction then hands off unresolved state without blocking", () => {
const cwd = makeWorkspace();
const env = { CLAUDE_PLUGIN_ROOT: path.resolve(testDir, "..") };
try {
writeState(cwd, validState({ evidence: "" }));
const first = parseOutput(runHook(cwd, { stop_hook_active: false }, env));
assert.equal(first.decision, "block");
assert.match(first.reason, /Update the note/);
const repeated = parseOutput(runHook(cwd, { stop_hook_active: true }, env));
assert.equal("decision" in repeated, false);
assert.equal("hookSpecificOutput" in repeated, false);
assert.equal(repeated.continue, true);
assert.match(repeated.systemMessage, /unresolved.*handoff/i);
assert.match(repeated.systemMessage, /empty section: Evidence/);
assert.match(repeated.systemMessage, /not complete/i);
writeState(cwd, validState({ evidence: '- clicked `<button>Save</button>`; checked `Map<K, V>` and <https://example.com>' }));
assert.equal(parseOutput(runHook(cwd, { stop_hook_active: true }, env)), null);
} finally { fs.rmSync(cwd, { recursive: true, force: true }); }
});

test("repeat guard does not change PreCompact or other platform protocols", () => {
const cwd = makeWorkspace();
try {
writeState(cwd, validState({ evidence: "" }));
const precompact = parseOutput(runHook(cwd, { hook_event_name: "PreCompact", stop_hook_active: true }, { CLAUDE_PLUGIN_ROOT: "root" }));
assert.equal(precompact.decision, "block");
const zcode = parseOutput(runHook(cwd, { stop_hook_active: true }, { ZCODE_PLUGIN_ROOT: "root" }));
assert.equal(zcode.decision, "block");
const workbuddy = parseOutput(runHook(cwd, { stop_hook_active: true }, { CODEBUDDY_PLUGIN_ROOT: "root" }));
assert.equal(workbuddy.continue, false);
const generic = parseOutput(runHook(cwd, { stop_hook_active: true }));
assert.equal(generic.continue, false);
} finally { fs.rmSync(cwd, { recursive: true, force: true }); }
});
Loading