New:Socket for Asana Is Now Available.Learn more
Get Started

dsh-graph

Package Overview
Dependencies
Maintainers
1
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

dsh-graph - npm Package Compare versions

Comparing version
0.4.3
to
0.5.1
+15
-3
core/events.js

@@ -47,4 +47,6 @@ /** 事件流:events.jsonl 是全部状态的唯一真相源(R-02)。 */

* 从事件流重建各目标状态:
* goal.created → draft;goal.planned 在 draft 时 → planning;goal.transition → details.to。
* goal.created → draft(无 version)或 planning(有 version);
* goal.planned 在 draft 时 → planning;goal.transition → details.to。
* (goal.planned 视为 planning 阶段的隐式进入,覆盖规划期未显式迁移的补记场景。)
* g-140:goal.deleted 为终态——replay 时标记已删除目标,后续事件忽略。
*/

@@ -56,4 +58,10 @@ export function replayStatuses(events) {

continue;
if (ev.event === "goal.created")
statuses.set(ev.goal, "draft");
// g-140:已删除目标不再响应任何事件
if (statuses.get(ev.goal) === "deleted")
continue;
if (ev.event === "goal.created") {
// g-137:带 version → 初始状态 planning;不带 version → draft
const hasVersion = ev.details?.version != null;
statuses.set(ev.goal, hasVersion ? "planning" : "draft");
}
if (ev.event === "goal.planned" && statuses.get(ev.goal) === "draft") {

@@ -67,4 +75,8 @@ statuses.set(ev.goal, "planning");

}
// g-140:goal.deleted 终态
if (ev.event === "goal.deleted") {
statuses.set(ev.goal, "deleted");
}
}
return statuses;
}
+5
-5

@@ -18,8 +18,8 @@ /** 目标状态机与迁移不变式(schema/SCHEMA.md §7)。 */

draft: new Set(["planning", "blocked"]),
planning: new Set(["collecting", "ready", "blocked"]), // planning→ready:无收集需求时直达(负责人 2026-08 指示)
collecting: new Set(["ready", "planning", "blocked"]),
planning: new Set(["collecting", "ready", "blocked", "in_progress"]), // planning→ready:无收集需求时直达;planning→in_progress:派发开发直接进执行(负责人 2026-08 指示)
collecting: new Set(["ready", "planning", "blocked", "in_progress"]), // collecting→in_progress:跳过 ready 直接执行(人工拖动视为授权)
ready: new Set(["in_progress", "collecting", "blocked"]),
in_progress: new Set(["review", "blocked"]),
in_progress: new Set(["review", "blocked", "collecting"]), // in_progress→collecting = 中断回退重新收集(负责人 2026-08-22)
review: new Set(["delivered", "in_progress", "blocked"]), // review→in_progress = 打回
delivered: new Set(),
delivered: new Set(["review"]), // delivered→review:负责人备注后回 review 补充/修 bug(负责人 2026-08-22)
blocked: new Set(), // 特殊处理:只能回 blocked_from

@@ -57,3 +57,3 @@ };

}
if (to === "in_progress") {
if (to === "in_progress" && !ctx.force) {
if (!meta.rules_snapshot) {

@@ -60,0 +60,0 @@ throw new GraphError("进入 in_progress 前必须记录 rules_snapshot");

@@ -6,3 +6,3 @@ /**

import { GraphError } from "./machine.js";
import { init, createGoal, setCriteria, transition, validate, rebuild, addCard, fillCard, reviewCard, startAttempt, reportStatus, moveGoal, amendGoal, } from "./ops.js";
import { init, createGoal, setCriteria, transition, validate, rebuild, addCard, fillCard, reviewCard, startAttempt, reportStatus, moveGoal, amendGoal, deleteGoal, archiveGoal, unarchiveGoal, } from "./ops.js";
function parseArgs(argv) {

@@ -59,3 +59,2 @@ const args = { root: ".dsh-graph", flags: new Map() };

version: flag(args, "version"),
scope: flagAll(args, "scope"),
actor,

@@ -143,2 +142,17 @@ });

}
case "archive-goal": {
archiveGoal(args.root, need(args, "goal"), { actor });
console.log("ok");
return;
}
case "unarchive-goal": {
unarchiveGoal(args.root, need(args, "goal"), { actor });
console.log("ok");
return;
}
case "delete-goal": {
deleteGoal(args.root, need(args, "goal"), { actor });
console.log("ok");
return;
}
case "validate": {

@@ -168,3 +182,3 @@ const problems = validate(args.root);

default:
throw new GraphError("用法:node core/main.ts [--root DIR] <init|create-goal|set-criteria|transition|add-card|fill-card|review-card|start-attempt|report-status|move-goal|amend-goal|validate|rebuild> [flags]");
throw new GraphError("用法:node core/main.ts [--root DIR] <init|create-goal|set-criteria|transition|add-card|fill-card|review-card|start-attempt|report-status|move-goal|amend-goal|archive-goal|unarchive-goal|delete-goal|validate|rebuild> [flags]");
}

@@ -171,0 +185,0 @@ }

@@ -97,1 +97,9 @@ /**

}
/** 判据小节的实质内容行数(去掉 HTML 注释后非空行;≥1 即视为已登记判据)。g-77647351 看板用。 */
export function countCriteria(body) {
const t = sectionText(body, "质量判据");
if (t === null)
return 0;
const stripped = t.replace(/<!--[\s\S]*?-->/g, "");
return stripped.split("\n").filter((l) => l.trim() !== "").length;
}
+244
-8

@@ -37,4 +37,8 @@ /**

amendGoal,
renameGoal,
requestAcceptReview,
resolveAccept,
archiveGoal,
unarchiveGoal,
deleteGoal,
boardProjection,

@@ -51,2 +55,3 @@ readSupervisorSession,

formatHarvestedCardsSection,
GraphError,
} from "./core/ops.js";

@@ -83,3 +88,3 @@ import { resolveRoot } from "./core/root.js";

"dsh-graph 是把工作组织成「目标看板」的插件。你有 graph_* 工具可用:",
"- graph_create_goal(title[, version, scope]) 建目标(进 backlog,带 version 则排期);",
"- graph_create_goal(title[, version]) 建目标(进 backlog,带 version 则排期);",
"- graph_set_criteria(goal, criteria[]) 先登记质量判据(判据先于执行,硬规则);",

@@ -89,2 +94,3 @@ "- graph_transition(goal, to[, reason]) 迁移状态;生命周期 draft→planning→collecting→ready→in_progress→review→delivered,另有 blocked(进 blocked 必须 reason);",

"- graph_start_attempt(goal) 派发执行子代理;graph_report_status(goal, attempt, status) 用一句 ≤20 字的话自报进展(看板卡片显示这句);",
"- graph_archive_goal(goal) 归档目标(仅 draft/planning/delivered 可归档);graph_unarchive_goal(goal) 取消归档;",
"- graph_amend_goal(goal, note) 记录修订/人工反馈;graph_validate / graph_rebuild 校验与对账。",

@@ -105,2 +111,16 @@ "原则:状态不是证据、产出物才是;每做一步主动迁移卡片、自报状态;不确定先问。",

// g-131:主管会话每 turn 自动注入简短纪律提醒(仅主管会话)。
// 提醒内容强调主管铁律:只做规划/派发/把关/复核、实现交子代理、每动作后
// graph_report_supervisor_status、review→delivered 必须等负责人 verdict。
// token 成本约 80 字,简短精炼。
const SUPERVISOR_DISCIPLINE = [
"⚠️ **主管纪律提醒**(每 turn 自动注入):",
"1. **只做规划、派发、把关、复核**——绝不自己实现、写代码、长调研;",
"2. 自己动手仅限:一句话决策、一行小修、graph_start_attempt 派发执行;",
"3. **每动作后 graph_report_supervisor_status**——看板实时显示状态;",
"4. **review→delivered 必须等负责人 verdict**——绝不自行 delivered;",
"5. 完整守则见 skill dsh-graph-supervisor(显式调用加载)。",
].join("\n");
// g-118:dsh-graph help 命令内容源(graph_help 工具输出 + 引导提示词指向它)。

@@ -110,3 +130,3 @@ // 使用说明 + claim 指引;不含主管守则(完整守则仍在 supervisor-guide.md / skill)。

"dsh-graph 是把工作组织成「目标看板」的插件。可用 graph_* 工具:",
"- graph_create_goal(title[, version, scope]) 建目标(进 backlog,带 version 则排期);",
"- graph_create_goal(title[, version]) 建目标(进 backlog,带 version 则排期);",
"- graph_set_criteria(goal, criteria[]) 先登记质量判据(判据先于执行,硬规则);",

@@ -118,2 +138,3 @@ "- graph_transition(goal, to[, reason]) 迁移状态;生命周期 draft→planning→collecting→ready→in_progress→review→delivered,另有 blocked(进 blocked 必须 reason);",

"- graph_amend_goal(goal, note) 记录修订/人工反馈;graph_validate / graph_rebuild 校验与对账;",
"- graph_archive_goal(goal) 归档目标(仅 draft/planning/delivered 可归档);graph_unarchive_goal(goal) 取消归档;",
"- graph_report_supervisor_status(status) 主管自报状态(看板顶部状态栏);graph_resolve_accept 评审裁决;",

@@ -136,3 +157,3 @@ "- graph_handoff() / graph_claim_supervisor() 换会话交接(g-117)。",

// 仍在主工作树写(graph_* 工具写的是主工作树的看板/事件流,不被 worktree 分支隔离)。
const WORKTREE_GUIDE = `【worktree 隔离(负责人 2026-08-22 指示)】并发/复杂的执行任务:先 \`git worktree add\` 一个独立工作树(与 main 隔离)再改代码,review 交付阶段由 supervisor 复核通过后合并回 main——避免并发子代理互相踩提交、半成品直接落 main。**简单的一两行改动、且与现有工作无冲突时,可直接在当前 main 分支修改,不必 worktree——worktree 与否由你自己决定**(本段由派发方开关:worktree=false 时省略)。
const WORKTREE_GUIDE = `【worktree 隔离(负责人 2026-08-22 指示)】并发/复杂的执行任务:先 \`git worktree add\` 一个独立工作树(与 main 隔离)再改代码,review 交付阶段由 supervisor 复核通过后合并回 main——避免并发子代理互相踩提交、半成品直接落 main。**「直接 main」仅限真正的一两行、唯一文件改动、且无其他目标并发改该文件;多目标并发改同一文件时必须 worktree,不得自认为改动简单就直改 main**(g-129/g-77647351 并发改 client.js 直 main 造成分叉冲突的教训)(本段由派发方开关:worktree=false 时省略)。
数据分工:代码改动在 worktree;看板数据 .dsh-graph/ 仍在主工作树写(graph_* 工具写的是主工作树的看板/事件流,不被 worktree 分支隔离,避免状态漂移)。`;

@@ -161,5 +182,5 @@

description: "创建目标(默认进 backlog;带 version 则排期入版本)。返回目标 id。",
parameters: params({ title: str, version: str, scope: strArr }, ["title"]),
parameters: params({ title: str, version: str }, ["title"]),
},
run: (a, ex) => ({ goal: createGoal(rootFor(ex), { title: a.title, version: a.version, scope: a.scope, actor: actorOf(ex) }) }),
run: (a, ex) => ({ goal: createGoal(rootFor(ex), { title: a.title, version: a.version, actor: actorOf(ex) }) }),
},

@@ -258,2 +279,13 @@ {

def: {
name: "graph_rename_goal",
description: "重命名目标:更新 goal.md 的 meta.title,记 goal.renamed 事件(旧/新标题)。title 非空、去首尾空白;相同标题为 no-op。",
parameters: params({ goal: str, title: str }, ["goal", "title"]),
},
run: (a, ex) => {
const result = renameGoal(rootFor(ex), a.goal, { title: a.title, actor: actorOf(ex) });
return { ok: true, ...result };
},
},
{
def: {
name: "graph_validate",

@@ -379,2 +411,5 @@ description: "全量不变式校验(状态、归属、判据、依赖环、卡片引用)。返回问题列表。",

bindAttemptChild(rootFor(ex), a.goal, attempt, started.childId, actorOf(ex), ex.agent?.session?.id);
// 负责人 2026-08-22:开始执行的目标必须落到执行 lane——派发成功后自动迁 in_progress
//(若已 in_progress 或门槛未满足则静默,子代理自行汇报)
try { transition(rootFor(ex), a.goal, "in_progress", { reason: "attempt 派发(graph_start_attempt)", actor: actorOf(ex) }); } catch { /* 已在 in_progress 或迁移被拒 */ }
result.child_id = started.childId;

@@ -414,2 +449,26 @@ if (effProvider || effModel) result.model_route = `${effProvider ?? "继承"}/${effModel ?? "继承"}`;

},
{
def: {
name: "graph_archive_goal",
description: "归档目标(仅 draft/planning/delivered 可归档)。移动到对应 archived 目录,记 goal.archived 事件。",
parameters: params({ goal: str }, ["goal"]),
},
run: (a, ex) => { archiveGoal(rootFor(ex), a.goal, { actor: actorOf(ex) }); return { ok: true }; },
},
{
def: {
name: "graph_unarchive_goal",
description: "取消归档目标(移回原位置,状态保持原样)。记 goal.unarchived 事件。",
parameters: params({ goal: str }, ["goal"]),
},
run: (a, ex) => { unarchiveGoal(rootFor(ex), a.goal, { actor: actorOf(ex) }); return { ok: true }; },
},
{
def: {
name: "graph_delete_goal",
description: "删除已归档目标(含其卡片/attempts 目录)。仅已归档目标可删除,且不能有活跃子代理。记 goal.deleted 事件。",
parameters: params({ goal: str }, ["goal"]),
},
run: (a, ex) => { deleteGoal(rootFor(ex), a.goal, { actor: actorOf(ex) }); return { ok: true }; },
},
];

@@ -461,6 +520,6 @@

const supervisorId = readSupervisorSession(rootForReq);
if (!supervisorId) return { supervisorId: null, parent: null, error: "未配置 supervisor.session(project.yaml)" };
if (!supervisorId) return { supervisorId: null, parent: null, error: "未配置 supervisor.session(project.yaml)——请先在该 workspace 运行 graph_claim_supervisor() 完成主管会话接管,再派发执行" };
const agents = ctx.get?.("agents");
const parent = agents?.get?.(supervisorId) ?? null;
if (!parent) return { supervisorId, parent: null, error: `主管会话 ${supervisorId} 无 live Agent(可能未在运行)` };
if (!parent) return { supervisorId, parent: null, error: `主管会话 ${supervisorId} 无 live Agent(可能未在运行)——请确认该主管会话已开启/在运行,或重新 graph_claim_supervisor()` };
return { supervisorId, parent, error: null };

@@ -536,3 +595,5 @@ } catch (e) {

try {
json(res, 200, boardPayload(rootForReq(_req)));
const sp = new URL(_req?.url ?? "", "http://x").searchParams;
const includeArchived = sp.get("includeArchived") === "1" || sp.get("includeArchived") === "true";
json(res, 200, boardPayload(rootForReq(_req), { includeArchived }));
} catch (e) {

@@ -593,3 +654,64 @@ json(res, 500, { error: String(e?.message ?? e) });

},
// g-77647351:transition 端点(拖放跨列触发状态迁移)
{
path: "/api/dsh-graph/transition",
handler: async (req, res) => {
try {
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
const body = await readBody(req);
const { goal, to, reason, force } = body;
if (!goal || !to) return json(res, 400, { error: "missing goal or to" });
transition(rootForReq(req, body), goal, to, { reason, force, actor: "human:gui" });
json(res, 200, { ok: true });
} catch (e) {
// GraphError → 400(参照 /accept 模式但用 400 而非 500)
const code = e instanceof GraphError ? 400 : 500;
json(res, code, { error: String(e?.message ?? e) });
}
},
},
// g-77647351:order 端点(排序持久化)
{
path: "/api/dsh-graph/order",
handler: async (req, res) => {
try {
const r = rootForReq(req);
const orderFile = join(r, "order.json");
if (req.method === "GET") {
try {
const data = JSON.parse(readFileSync(orderFile, "utf8"));
json(res, 200, data);
} catch {
json(res, 200, {});
}
} else if (req.method === "POST") {
const body = await readBody(req);
writeFileSync(orderFile, JSON.stringify(body, null, 2), "utf8");
json(res, 200, { ok: true });
} else {
json(res, 405, { error: "method not allowed" });
}
} catch (e) {
json(res, 500, { error: String(e?.message ?? e) });
}
},
},
// g-77647351:move-goal 端点(跨 lane 拖放触发归属变更)
{
path: "/api/dsh-graph/move-goal",
handler: async (req, res) => {
try {
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
const body = await readBody(req);
const { goal, to, version } = body;
if (!goal || !to) return json(res, 400, { error: "missing goal or to" });
moveGoal(rootForReq(req, body), goal, { to, version, actor: "human:gui" });
json(res, 200, { ok: true });
} catch (e) {
const code = e instanceof GraphError ? 400 : 500;
json(res, code, { error: String(e?.message ?? e) });
}
},
},
{
path: "/api/dsh-graph/edit-description",

@@ -610,2 +732,18 @@ handler: async (req, res) => {

{
path: "/api/dsh-graph/rename-goal",
handler: async (req, res) => {
try {
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
const body = await readBody(req);
const { goal, title } = body;
if (!goal || !title || typeof title !== "string") return json(res, 400, { error: "missing goal or title" });
const result = renameGoal(rootForReq(req, body), goal, { title, actor: "human:gui" });
json(res, 200, { ok: true, ...result });
} catch (e) {
const code = e instanceof GraphError ? 400 : 500;
json(res, code, { error: String(e?.message ?? e) });
}
},
},
{
path: "/api/dsh-graph/add-card",

@@ -715,2 +853,4 @@ handler: async (req, res) => {

bindAttemptChild(rRoot, goal, attempt, spawned.childId, "human:gui", spawned.parentSessionId);
// 负责人 2026-08-22:执行按钮派发后目标必须落到执行 lane——自动迁 in_progress
try { transition(rRoot, goal, "in_progress", { reason: "attempt 派发(GUI 执行)", actor: "human:gui" }); } catch { /* 已在 in_progress 或迁移被拒 */ }
}

@@ -734,2 +874,72 @@ json(res, 200, { ok: true, attempt, child_id: spawned.childId, child_error: spawned.error, model_route: spawned.model_route ?? null, injected_cards: injectedCards });

},
// g-129: 新增创建目标端点
{
path: "/api/dsh-graph/create-goal",
handler: async (req, res) => {
try {
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
const body = await readBody(req);
const { title, version, description } = body;
if (!title || typeof title !== "string" || !title.trim()) {
return json(res, 400, { error: "missing title" });
}
const r = rootForReq(req, body);
const goalId = createGoal(r, { title: title.trim(), version, description, actor: "human:gui" });
json(res, 200, { ok: true, goal: goalId });
} catch (e) {
json(res, 500, { error: String(e?.message ?? e) });
}
},
},
// g-110: 归档目标端点
{
path: "/api/dsh-graph/archive",
handler: async (req, res) => {
try {
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
const body = await readBody(req);
const { goal } = body;
if (!goal) return json(res, 400, { error: "missing goal" });
archiveGoal(rootForReq(req, body), goal, { actor: "human:gui" });
json(res, 200, { ok: true });
} catch (e) {
const code = e instanceof GraphError ? 400 : 500;
json(res, code, { error: String(e?.message ?? e) });
}
},
},
// g-110: 取消归档目标端点
{
path: "/api/dsh-graph/unarchive",
handler: async (req, res) => {
try {
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
const body = await readBody(req);
const { goal } = body;
if (!goal) return json(res, 400, { error: "missing goal" });
unarchiveGoal(rootForReq(req, body), goal, { actor: "human:gui" });
json(res, 200, { ok: true });
} catch (e) {
const code = e instanceof GraphError ? 400 : 500;
json(res, code, { error: String(e?.message ?? e) });
}
},
},
// g-140: 删除已归档目标端点
{
path: "/api/dsh-graph/delete",
handler: async (req, res) => {
try {
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
const body = await readBody(req);
const { goal } = body;
if (!goal) return json(res, 400, { error: "missing goal" });
deleteGoal(rootForReq(req, body), goal, { actor: "human:gui" });
json(res, 200, { ok: true });
} catch (e) {
const code = e instanceof GraphError ? 400 : 500;
json(res, code, { error: String(e?.message ?? e) });
}
},
},
];

@@ -771,4 +981,30 @@

}));
// g-131:主管会话每 turn 自动注入简短纪律提醒(仅主管会话)。
// text(context) 里取 sessionId=context?.agent?.session?.id;
// 再取 cwd=context?.agent?.session?.header?.cwd(当前会话 workspace);
// 用 resolveRoot(config, cwd) 得该项目 .dsh-graph;readSupervisorSession(该项目root);
// supervisorId===sessionId 时返回 SUPERVISOR_DISCIPLINE,否则空。
// cwd 缺失则不注入(避免误注入)。
disposers.push(sp.section({
name: "dsh-graph-supervisor-discipline",
order: 11,
text: (context) => {
try {
const sessionId = context?.agent?.session?.id;
if (!sessionId) return "";
// 读当前会话 workspace 的项目 .dsh-graph/project.yaml 的 supervisor.session
const cwd = context?.agent?.session?.header?.cwd;
if (!cwd) return ""; // cwd 缺失则不注入(避免误注入)
const projectRoot = resolveRoot(config, cwd);
const supervisorId = readSupervisorSession(projectRoot);
if (!supervisorId || supervisorId !== sessionId) return "";
return "\n" + SUPERVISOR_DISCIPLINE;
} catch {
return "";
}
},
}));
sectionState.registered = true;
process.stderr.write(`[dsh-graph-host] g-118: guide hint section 已注册(所有会话注入引导提示词,root=${root})\n`);
process.stderr.write(`[dsh-graph-host] g-131: supervisor discipline section 已注册(仅主管会话注入纪律提醒,按会话 workspace 解析)\n`);
} catch (e) {

@@ -775,0 +1011,0 @@ console.error("[dsh-graph-host] g-118 guide hint section 注册失败:", e?.message ?? e);

{
"name": "dsh-graph",
"version": "0.4.3",
"version": "0.5.1",
"description": "dsh-graph 单包(host+client 合并,g-116):把 dsh-graph 核心层包装为 DSH cordis 插件,同时提供 graph_* 目标生命周期工具(建卡/判据/迁移/派发/评审/交付)、/api/dsh-graph REST 端点与浏览器二维泳道看板(conversation.view)。包名 = repo 名(负责人定案);内部 host 插件 id 保留 dsh-graph-host。",

@@ -52,4 +52,5 @@ "license": "MIT",

"scripts": {
"build": "bash ../scripts/sync-core.sh",
"test": "node --test ../core/tests/*.test.ts"
}
}

@@ -11,2 +11,18 @@ ---

## 接手前置:确认已接管主管角色
**开始任何主管工作前,先确认你已接管本 workspace 的主管角色**(否则看板主管栏/
执行派发/live 子代理都找不到主管会话):
- 读 `project.yaml` 的 `supervisor.session`,或看 `graph_help` 的接管指引;
- 若**未配置 / 未指向本会话**:说明该 workspace 还没建立主管 → 运行
`graph_claim_supervisor()` 由本会话接管(自动更新 `project.yaml` 的
`supervisor.session`、记 `supervisor.claimed` 事件、返回 HANDOFF 全文);
- **例外(g-118 防争抢)**:若 `supervisor.session` **已指向其他会话**且负责人
**没要求**你接管,则**不要 claim**——保持普通会话身份,避免与既有主管争抢角色;
此时你只做普通工作,需接管时等负责人明确指示。
> 只有 `graph_claim_supervisor()` 会写 `supervisor.session`;加载本 skill 本身**不会**
> 接管(g-118 防止临时会话无意争抢主管)。本 workspace 无任何进程接管时,由你显式接管。
> **⚠️ 首要铁律(违反即降级)**:supervisor **只做规划、派发、把关、复核**,

@@ -52,2 +68,10 @@ > **绝不自己实现功能、绝不自己写大段代码、绝不自己长调研**——所有实现/调研/

做判据核验。不要与负责人抢着指挥同一个子代理。
10. **`graph_amend_goal` 的 note vs append(负责人 2026-08-23 定)**:
- `note`(必填):修订备注,**只记 `goal.amended` 事件**,**不写进目标描述正文**——
用于轻量/过程性/跨目标备注(如「已复核」「已派发 g-131」「这条转 g-138 承接」)。
- `append`(可选):**写进目标描述正文**(并入 `## 目标描述` 小节)——用于会影响
目标范围/需求/设计、执行者与看板应读到的**内容**(需求、反馈、设计方案、约束、决策理由)。
- **判定**:凡属"目标自身内容"(要落实、要被执行者/看板看到)→ 用 `append` 写入正文;
凡属"过程/事件/仅留痕"(不改目标描述)→ 只用 `note`。**拿不准就 `append`**(落进正文
最不易丢),纯 `note` 只用于确认/过程性话。append 只传正文、勿自带 `## 标题`(见 #7)。

@@ -79,2 +103,11 @@ ## 阶段推进规范

6. **交付前置(负责人 2026-08-22 定,2026-08-23 细化 commit 归属):到 delivered 的目标,其改动必须已 git commit**——
但**区分谁、何时 commit**:
- **worktree 开发**(隔离分支):子代理在 worktree 内 commit OK;supervisor 复核通过后
merge/`git checkout --` 到 main(只合代码,别重置 `.dsh-graph`)。
- **直接 main 开发**:子代理**不提前 commit**——review 还会修 bug,提前提交会产生碎
提交/与后续修改冲突。正确:子代理把**改动留在工作树不提交**,supervisor 复核+
修完 bug 后,**统一提交一个最终 commit**(交付前置:delivered 前该目标改动已落库)。
- 即:commit 由 supervisor 在交付前**统一收口**;子代理无需(也不应)在 main 上抢提交。
要点:状态迁移一律走工具(事件先行,R-02),**绝不手改 frontmatter 状态

@@ -168,2 +201,6 @@ 字段**;判据确认与 review verdict 是人工 gate,停轮等输入,不用自动续轮

会保留 status 汇报继续工作,supervisor 只需在复核时把关状态与产出一致;
**执行派发自动落执行 lane(负责人 2026-08-22 补充)**:`graph_start_attempt`
工具与 GUI「执行」按钮派发成功后**自动 transition 到 in_progress**(引擎层
start-execution/工具已内置,避免子代理漏移、目标滞留收集/ready lane);
若迁移被拒(门槛未满足),supervisor 复核时注意把关;
**禁区:执行子代理不得自移 `review→delivered`**——delivered 是 human gate,

@@ -179,9 +216,9 @@ 只有负责人 verdict 通过后由 supervisor 执行(g-112 教训:执行方误把

- 冻结脚本路径、验收命令逐条写全;
- **worktree 隔离(负责人 2026-08-22 指示,含 2026-08-22 细化)**:并发/复杂的
- **worktree 隔离(负责人 2026-08-22 指示,含 2026-08-22 二次强化)**:并发/复杂的
执行任务,子代理宜先 `git worktree add` 独立工作树(与 main 隔离)再改代码,
review 交付阶段由 supervisor 复核通过后合并回 main——避免并发子代理互相踩提交、
避免半成品直接落 main。**简单的一两行改动、且与现有工作无冲突时,子代理可直接
在 main 分支修改,不必 worktree**(负责人 2026-08-22 细化:worktree 与否由子代理
自己决定,supervisor 不代劳合并、不强行套 worktree——自动合并 worktree 反而给
子代理造成困扰);
避免半成品直接落 main。**「直接 main」仅限真正的一两行、且是唯一改动的文件、且
无其他目标并发改该文件**(负责人 2026-08-22 二次强化:g-129 与 g-77647351 并发改
client.js 都直接 main,造成分叉冲突、merge 地狱——**多目标并发改同一文件时,子代理
必须 worktree**,不得因「自认为改动简单」而直接 main);
worktree 指令(g-120)由执行派发默认注入 spawn 提示词,可显式关闭跳过:

@@ -192,2 +229,9 @@ `graph_start_attempt` 传 `worktree=false`、GUI 端点

(graph_* 工具写的是主工作树的看板/事件流,不被 worktree 分支隔离);
- **只在仓库根跑 graph_* 工具(负责人 2026-08-22)**:执行/调研子代理务必以
**仓库根**为工作目录跑 graph_* 工具,**绝不在包目录(如 `dsh-graph-host/`)下跑**——
否则工具会按会话 cwd 在包目录自动 init 出一个 `.dsh-graph/` 骨架(「子代理误建数据
目录」的已知问题,曾多次清理)。该目录非项目数据,已 gitignore
(`dsh-graph-host/.dsh-graph/`)防 git 污染,但会弄乱工作区——子代理应统一在
仓库根的 `.dsh-graph/` 读写看板数据;supervisor 派发时若发现子代理 cwd 落在包目录,
及时纠正。

@@ -206,3 +250,8 @@ - **模型路由**:执行子代理**不继承父会话模型**——统一走 project.yaml 的

执行方不得修改;脚本报错优先怀疑实现与设计,不是脚本。
- **发现排期/归属变化先查事件 actor(负责人 2026-08-23)**:supervisor 发现目标被移动/改排期
(backlog↔版本↔独立变化)时,**先看该卡片 `goal.moved` / `goal.transition` 事件的 actor**——
若为 `human:gui`(负责人 GUI 操作),说明是负责人刻意为之,**不要刻意恢复/纠正**,按新归属为准;
只有非用户改动且与设计冲突时才复核/纠正。先核实再行动(g-126 教训:别只看表面变化就断言并动手)。
## 环境事实与排查(必读,来自历次翻车)

@@ -209,0 +258,0 @@

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display