+15
-3
@@ -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"); |
+17
-3
@@ -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 @@ } |
+8
-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); |
+2
-1
| { | ||
| "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" | ||
| } | ||
| } |
+54
-5
@@ -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
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
339353
38.88%6261
41.78%25
150%