diff --git a/codex/skills/threadsmith/SKILL.md b/codex/skills/threadsmith/SKILL.md index c671a21..b2d9dcd 100644 --- a/codex/skills/threadsmith/SKILL.md +++ b/codex/skills/threadsmith/SKILL.md @@ -159,13 +159,43 @@ Choose the smallest writeback tier that preserves safety: - `evidence-only`: no committed truth state changes. Evidence may live in the final response, command output, local run artifact, or explicitly configured - runtime evidence artifact. Do not mutate project state files. + runtime evidence artifact. Do not mutate project state files. Prefer ignored + or temporary paths for local artifacts; if ignored status is unknown, label + it as an `untracked artifact risk`. - `current-context`: update current packet, active work, or evidence summary only because the next operator turn needs that fact. - `committed-truth`: update phase, acceptance, status, supervision, role packets, handoff, proposal review, or phase history because durable project state changed. +Writeback file allowlist: + +- `evidence-only`: default to 0 `.threadsmith` state-file writes. Use final + response, command output, test output, or ignored/temp local artifact. +- `current-context`: may update only next-turn context/evidence such as + `.threadsmith/context/current-packet.json`, + `.threadsmith/context/evidence-summary.json`, or `.threadsmith/active-work.json` + when that fact affects the next action. +- `committed-truth`: may update durable files such as + `.threadsmith/current-phase.json`, `.threadsmith/acceptance-state.json`, + `.threadsmith/project-status.json`, project brief/roadmap/supervision, role + packets, handoff/routing files, or `.threadsmith/history/phases.jsonl` only + when those surfaces actually changed. + +Do not create optional context files solely to satisfy a tier, and do not rewrite +role packets when they only restate current packet facts. + +Runtime recommendations should include surface metadata when determinable: + +- `surfaceAudience`: `internal`, `developer`, `operator`, or `user_public` +- `workVisibility`: `internal`, `developer_visible`, `operator_visible`, or + `user_visible` + +`operator` surfaces may continue inside an approved scope when local, +reversible, and not changing long-term workflow semantics. Upgrade them to +`full-governance` when they create a long-lived operator/public entry point, +alter defaults, affect compatibility, or could be mistaken for public behavior. + Short approvals such as "同意" do not create committed truth by themselves. They execute the accepted step unless they also change scope, product direction, architecture, acceptance, or durable route. @@ -338,15 +368,16 @@ Full output must use the exact field skeleton in `Output Contract`. Do not satisfy full output by writing only these section headings with free-form paragraphs underneath. -Full output sections, each with required child fields: +Full output sections, each with required child fields. Use human-first order: -1. `Threadsmith Decision` +1. `一句话结论` 2. `本 phase 的结果` 3. `这一步具体做了什么` 4. `这一步解决的问题` 5. `验证` 6. `下一 phase 预览` 7. `你需要审核的点` +8. `Threadsmith Decision` Internal progress output: @@ -501,19 +532,8 @@ When using the full output, render this exact skeleton. Keep answers concise, but do not omit required labels. If a field has no content, write `none` or `not run` with the reason. -### Threadsmith Decision -- mode: `sync`, `drive`, `continuous`, or `recover` -- accepted previous recommendation: yes / no -- source layer: committed truth / role packet / Context Packet / repo evidence / chat memory -- project state: -- current phase state: -- acceptance state: -- selected role and role packet status: -- role-chain status: internal continuing / stopped at closeout / blocked -- action taken now or blocking gate: -- last completed step: -- next best step: -- active gate or stop condition: +### 一句话结论 +- 先用 1-2 句中文说明:这一步到底让项目多了什么能力、现在停在哪里、下一步要用户审核什么。不要从 protocol field 开场。 ### 本 phase 的结果 - phase 名称: @@ -541,13 +561,27 @@ but do not omit required labels. If a field has no content, write `none` or ### 下一 phase 预览 - Phase:候选 phase 名称 - continuity: new / continue / consolidate / gap-check / handoff / blocked -- Why now:为什么现在做这一步,不做会卡在哪里 -- Questions:这一 phase 要回答哪些具体问题 +- Why now:为什么现在做这一步,不做会卡在哪里;必须说明它接的是上一 phase 的哪个结果 +- Questions:这一 phase 要回答哪些具体问题;不要只写“继续检查” - Deliverables:会产出什么,不要只写“优化”或“继续推进”;每个交付物都要说明它对操作者或系统能力的意义 - Non-goals:明确不会做什么 - Done when:怎么判断完成 - Stop condition:遇到什么情况必须停下或让用户决策 +If there are multiple options, pick one recommendation first and explain the +tradeoff in the same fields. Do not output only `Option A` / `Option B` bullets +without `Why now`, `Deliverables`, and `Done when`. + +### Threadsmith Decision +- mode: `sync`, `drive`, `continuous`, or `recover` +- source layer: committed truth / role packet / Context Packet / repo evidence / chat memory +- role-chain status: internal continuing / stopped at closeout / blocked +- active gate or stop condition: + +Keep this section last and compact. Do not include long project summaries, +full role lists, last-step details, or next-step explanations here. Put those +in the human-facing sections above. + Only use this section for a real next phase or phase-boundary closeout. If the next action is merely reviewer, verifier, or closeout inside the current phase, describe it as `下一内部 gate` in the current section and continue unless a @@ -564,7 +598,26 @@ stop condition applies. - Every next step and closeout must include capability translation: name the technical object and the project capability it enables. - If there is no user-visible capability yet, say so explicitly and name the layer that changed, such as "这只是底层 runner,还不是 CLI、按钮或前端入口。" -- Keep `Threadsmith Decision` compact. Detailed explanation belongs in `本 phase 的结果`, `这一步具体做了什么`, and `下一 phase 预览`. +- Keep `Threadsmith Decision` compact and at the end. Detailed explanation belongs in `一句话结论`, `本 phase 的结果`, `这一步具体做了什么`, and `下一 phase 预览`. + +Explanation style preference: + +- `operatorExplanationStyle`: `concise`, `balanced`, `teaching`, or `detailed` +- source priority: project preferences, AGENTS.md, project brief/supervision, + then Threadsmith default `balanced` +- this changes explanation depth only; it must not change safety gates, + verification level, or writeback tier + +Timestamp and command comfort: + +- durable truth timestamps use new-write-only UTC ISO 8601 with milliseconds + (`YYYY-MM-DDTHH:mm:ss.SSSZ`). Do not bulk-rewrite legacy timestamps solely for + normalization. +- before recommending repo commands such as + `npm run threadsmith:autopilot -- continue `, verify the command + exists in the target repo, then Threadsmith control deck, then global command + lookup. If availability cannot be safely checked, provide the manual + equivalent instead of presenting the command as executable. If `accepted previous recommendation` is `yes`, this section must describe the step being executed now or the blocking gate. It must not repeat the same diff --git a/codex/skills/threadsmith/references/action-contracts.md b/codex/skills/threadsmith/references/action-contracts.md index 480cf50..23791c2 100644 --- a/codex/skills/threadsmith/references/action-contracts.md +++ b/codex/skills/threadsmith/references/action-contracts.md @@ -359,13 +359,42 @@ Use one writeback tier per action: - `evidence-only`: no committed truth state changes; evidence may live in the final response, command output, local run artifact, or explicitly configured - runtime evidence artifact + runtime evidence artifact. Prefer ignored/temp paths; if ignored status is + unknown, label the artifact as an `untracked artifact risk`. - `current-context`: update current packet, active work, or evidence summary only because the next operator turn needs that fact - `committed-truth`: update phase, acceptance, status, supervision, role packets, handoff, proposal review, or phase history because durable project state changed +Writeback file allowlist: + +- `evidence-only`: default to 0 `.threadsmith` state-file writes. Use final + response, command output, test output, or ignored/temp local artifact. +- `current-context`: may update only next-turn context/evidence such as + `.threadsmith/context/current-packet.json`, + `.threadsmith/context/evidence-summary.json`, or `.threadsmith/active-work.json` + when that fact affects the next action. +- `committed-truth`: may update durable files such as + `.threadsmith/current-phase.json`, `.threadsmith/acceptance-state.json`, + `.threadsmith/project-status.json`, project brief/roadmap/supervision, role + packets, handoff/routing files, or `.threadsmith/history/phases.jsonl` only + when those surfaces actually changed. + +Do not create optional context files solely to satisfy a tier, and do not rewrite +role packets when they only restate current packet facts. + +Runtime recommendations should include surface metadata when determinable: + +- `surfaceAudience`: `internal`, `developer`, `operator`, or `user_public` +- `workVisibility`: `internal`, `developer_visible`, `operator_visible`, or + `user_visible` + +`operator` surfaces may continue inside an approved scope when local, +reversible, and not changing long-term workflow semantics. Upgrade them to +`full-governance` when they create a long-lived operator/public entry point, +alter defaults, affect compatibility, or could be mistaken for public behavior. + Short approvals do not create committed truth by themselves. They execute the accepted step unless they also change scope, product direction, architecture, acceptance, or durable route. @@ -531,14 +560,17 @@ When the gate triggers, do not use a compact prose closeout like: ``` That shape loses the operator orientation contract. The same content must be -placed under `本 phase 的结果`, `这一步具体做了什么`, `这一步解决的问题`, -`验证`, `下一 phase 预览`, and `你需要审核的点`. +placed under `一句话结论`, `本 phase 的结果`, `这一步具体做了什么`, +`这一步解决的问题`, `验证`, `下一 phase 预览`, and `你需要审核的点`. The Threadsmith closeout contract overrides ordinary concise final-answer style. -Use this exact field skeleton. Do not satisfy the rule with section headings -and free-form paragraphs only: +Use this exact human-first field skeleton. Do not satisfy the rule with section +headings and free-form paragraphs only: +- `一句话结论`: 1-2 Chinese sentences explaining what capability changed, where + the work stopped, and what the operator needs to approve next. Do not start + a closeout with protocol fields. - `本 phase 的结果`: `phase 名称`, `result`, `交付物`, `结果一句话`, and `架构影响`. - `这一步具体做了什么`: `Before`, `Changed`, `After`, and `Not changed`. @@ -549,6 +581,8 @@ and free-form paragraphs only: `Why now`, `Questions`, `Deliverables`, `Non-goals`, `Done when`, and `Stop condition`. - `你需要审核的点`: only route, scope, non-goal, or acceptance decisions. +- `Threadsmith Decision`: compact protocol footer only: `mode`, `source layer`, + `role-chain status`, and `active gate or stop condition`. The narrative should be concrete enough that the operator can answer: @@ -563,6 +597,11 @@ Avoid vague labels such as "continue optimization" or "improve workflow" unless they are immediately translated into questions, deliverables, and done-when criteria. +When multiple next-step options are reasonable, recommend one first and explain +the tradeoff in the required fields. Do not output only `Option A` / `Option B` +bullets without `Why now`, `Deliverables`, and `Done when`; that makes the +operator decode the plan instead of reviewing it. + ## Operator Translation Rule Threadsmith must not make the operator decode implementation vocabulary. @@ -585,10 +624,25 @@ For each important technical object, answer: - what the operator or system can do now because this object exists; - whether it is already user-facing or still only an internal foundation. -`Threadsmith Decision` should stay compact and may contain protocol fields. -The explanatory burden belongs in `本 phase 的结果`, `这一步具体做了什么`, +`Threadsmith Decision` should stay compact, appear at the end, and contain only +the minimum protocol fields needed for auditability. The explanatory burden +belongs in `一句话结论`, `本 phase 的结果`, `这一步具体做了什么`, `这一步解决的问题`, and `下一 phase 预览`. +`operatorExplanationStyle` may be `concise`, `balanced`, `teaching`, or +`detailed`. It changes explanation depth only; it must not change safety gates, +verification level, or writeback tier. + +Durable truth timestamps use new-write-only UTC ISO 8601 with milliseconds +(`YYYY-MM-DDTHH:mm:ss.SSSZ`). Do not bulk-rewrite legacy timestamps solely for +normalization. + +Before recommending commands such as +`npm run threadsmith:autopilot -- continue `, check the target +repo first, then Threadsmith control deck, then global command availability. If +availability cannot be safely checked, provide the manual equivalent instead of +presenting the command as executable. + ## Next-Step Continuity Rule Every `next step` statement must first decide how the proposed move relates to diff --git a/codex/skills/threadsmith/references/role-contracts.md b/codex/skills/threadsmith/references/role-contracts.md index 3f81b20..39fbe01 100644 --- a/codex/skills/threadsmith/references/role-contracts.md +++ b/codex/skills/threadsmith/references/role-contracts.md @@ -171,19 +171,35 @@ Allowed writes: - `.threadsmith/project-status.json` - `.threadsmith/active-work.json` - `.threadsmith/project-supervision.json` -- docs or changelog updates if required by the slice +- docs or changelog updates only when required by the slice and durable enough + for future recovery, audit, public docs, architecture decisions, or explicit + operator request + +Writeback tier guard: + +- `evidence-only`: do not mutate committed state. Use final response, command + output, test output, or ignored/temp artifact. +- `current-context`: update only next-turn context/evidence such as current + packet, evidence summary, or active work when the next action needs it. +- `committed-truth`: update acceptance/status/supervision/phase history only + when durable project state changed. Forbidden writes: - new implementation scope - hiding residual risk - accepting without verification evidence +- creating closeout reports by default for light repairs or ordinary work + sessions +- rewriting role packets when they only restate current packet facts Completion artifact: - closeout summary, cleanup result, residual risks, and next planned slice - capability translation: what changed for the project or operator, not only which file or state object changed +- if a durable report is created, state why it is durable; otherwise final + response plus evidence summary / verification output is enough ## Hygiene diff --git a/codex/skills/threadsmith/references/runtime-contract.md b/codex/skills/threadsmith/references/runtime-contract.md index 96b8f8c..0dc88cf 100644 --- a/codex/skills/threadsmith/references/runtime-contract.md +++ b/codex/skills/threadsmith/references/runtime-contract.md @@ -316,6 +316,22 @@ deterministic from state: `full-governance` - writeback tier: `evidence-only`, `current-context`, or `committed-truth` - verification level: `narrow`, `standard`, or `release` +- surface audience: `internal`, `developer`, `operator`, or `user_public` +- work visibility: `internal`, `developer_visible`, `operator_visible`, or + `user_visible` + +For heartbeat-compatible closeout evidence, record these fields when available: + +- `governanceMode`: `light-repair`, `normal-implementation`, or + `full-governance` +- `surfaceAudience`: `internal`, `developer`, `operator`, or `user_public` +- `workVisibility`: `internal`, `developer_visible`, `operator_visible`, or + `user_visible` +- `valueHeartbeatShown`: `true`, `false`, or `skipped` + +Prefer phase history as the long-term counter source. If phase history does not +exist, use evidence summary. Final responses are operator display, not durable +counter storage. Missing legacy metadata is safe only when the action has no hard stop, no stale truth, no release / destructive / provider / cross-agent state risk, and no @@ -324,7 +340,39 @@ claim of durable phase acceptance. When unsafe, fall back to Evidence-only actions must not mutate project state files. They may leave evidence in the final response, command output, local run artifact, or an -explicitly configured runtime evidence artifact. +explicitly configured runtime evidence artifact. Prefer ignored/temp paths for +local artifacts; if ignored status is unknown, surface an `untracked artifact +risk` instead of treating the artifact as clean. + +Writeback file allowlist: + +- `evidence-only`: default to 0 `.threadsmith` state-file writes. +- `current-context`: may update only next-turn context/evidence such as + `.threadsmith/context/current-packet.json`, + `.threadsmith/context/evidence-summary.json`, or `.threadsmith/active-work.json`. +- `committed-truth`: may update durable state such as current phase, + acceptance, project status, project brief/roadmap/supervision, role packets, + handoff/routing files, or phase history when those surfaces actually changed. + +Do not create optional context files solely to satisfy a tier, and do not rewrite +role packets when they only restate current packet facts. + +Operator comfort metadata: + +- `operatorExplanationStyle`: `concise`, `balanced`, `teaching`, or `detailed` +- source priority: project preferences, AGENTS.md, project brief/supervision, + then Threadsmith default `balanced` +- explanation style changes explanation depth only; it must not change safety + gates, verification level, or writeback tier + +Timestamp rule: + +- durable truth timestamps use new-write-only UTC ISO 8601 with milliseconds + (`YYYY-MM-DDTHH:mm:ss.SSSZ`) +- legacy timestamps remain valid unless their owning file is touched for another + approved reason +- do not bulk-rewrite historical `.threadsmith` files only to normalize + timestamps ## Context Packet Current-State Budget diff --git a/docs/plans/threadsmith-noise-reduction-operator-comfort-v1.md b/docs/plans/threadsmith-noise-reduction-operator-comfort-v1.md new file mode 100644 index 0000000..77ff94b --- /dev/null +++ b/docs/plans/threadsmith-noise-reduction-operator-comfort-v1.md @@ -0,0 +1,505 @@ +# Threadsmith Noise Reduction and Operator Comfort v1 Implementation Plan + +Goal: make Threadsmith feel lighter in real projects by reducing unnecessary +state writeback, narrowing stop gates, making closeouts human-first, and +preserving safety only where the risk actually justifies full governance. + +Scope: improve the generic Threadsmith skill and runtime recommendation rules +for ordinary development sessions. This plan targets protocol noise, closeout +heaviness, confusing next-step recommendations, over-eager document creation, +truth writeback churn, and operator-facing explanation quality. + +Non-goals: Asterlea-specific behavior, frontend redesign, release automation, +removing verification, weakening destructive / credential / release gates, +removing committed truth, or replacing project-level AGENTS.md guidance. + +Assumptions: + +- The Human-Centered Governance v1 and Adaptive Work Session Mode v1 contracts + are the baseline. +- Threadsmith should remain a safety belt and project log, not the product + direction owner. +- The same improvements should work for app projects, libraries, CLIs, + creative-engineering projects, docs-heavy projects, and cross-agent projects. +- Project-specific teaching language belongs in AGENTS.md, project brief, or + preferences; Threadsmith core should provide generic hooks and defaults. + +Verification: + +- `npm run verify:skill-contract` +- `npm run test --workspace @threadsmith/domain` +- `npm run test --workspace @threadsmith/runtime` +- `npm run test --workspace @threadsmith/orchestrator` +- targeted fs-bridge tests if writeback code changes +- `npm run verify:project-truth` +- `git diff --check` +- `npm run verify:skill-sync` only after an intentional global skill sync + +## Problem Summary + +Threadsmith is now meaningfully better at avoiding repeated approvals and +recommendation loops, but it can still make ordinary development feel like a +governance exercise. + +Current failure modes: + +- small tasks can still create large `.threadsmith` diffs; +- small developer-only entries can be treated like major public surfaces; +- closeout can still sound like a protocol report instead of a development + partner's summary; +- next-step recommendations can name a phase without explaining the concrete + work, affected layer, and done-when evidence; +- value heartbeat is advisory but not strong enough to counter long runs of + internal-only governance; +- short approval inheritance is useful but unsafe when the previous + recommendation was ambiguous; +- docs and closeout artifacts can become default output instead of durable + artifacts only when future readers need them. + +The intended shift: + +```text +from: every completed slice deserves full truth and full closeout +to: each slice gets the smallest durable record that preserves safety, recovery, and next-step clarity +``` + +## Design Principles + +- Make the default operator experience human-first, then audit-friendly. +- Treat full governance as a safety tier, not a default writing style. +- Prefer work sessions for related small actions inside an accepted direction. +- Preserve strict stops for public semantics, compatibility, release, + destructive actions, credentials, provider defaults, and cross-agent state. +- Do not write long-lived docs or committed truth unless another thread, + another agent, or a future recovery path will realistically need them. +- Always translate protocol and architecture terms into plain operator language + when they appear in user-facing output. +- When the user says "同意", continue only if the previous recommendation was + unique, concrete, in scope, and executable. + +## Files + +- Modify: `codex/skills/threadsmith/SKILL.md` +- Modify: `codex/skills/threadsmith/references/action-contracts.md` +- Modify: `codex/skills/threadsmith/references/runtime-contract.md` +- Modify: `codex/skills/threadsmith/references/role-contracts.md` +- Modify: `scripts/verify-threadsmith-skill-contract.mjs` +- Modify: `packages/domain/src/preferences.ts` +- Modify: `packages/domain/src/preferences.test.ts` +- Modify: `packages/runtime/src/nextBestStep.ts` +- Modify: `packages/runtime/src/nextBestStepModel.ts` +- Modify: `packages/runtime/src/nextBestStep.test.ts` +- Modify: `packages/runtime/src/humanCenteredOutput.test.ts` +- Modify or create: `packages/runtime/fixtures/human-centered-output/*.json` +- Optional modify: `packages/fs-bridge/src/*` only if writeback tier behavior is + enforced in fs-bridge rather than documented at the skill/runtime layer. +- Optional modify: `packages/orchestrator/src/*` only if short approval routing + or command capability detection is implemented there. +- Optional create: `docs/reports/threadsmith-noise-reduction-operator-comfort-v1-closeout.md`. + Create it only if this change needs durable audit evidence because it changes + the global skill contract or future recovery behavior. Ordinary small tasks + should not create a report by default. + +## Implementation Order + +Implement in risk/value order rather than trying to change every surface at +once: + +1. Output and closeout shape: human-first full output, compact protocol footer, + next-step concreteness, verifier / fixture updates. +2. Writeback tier hardening: file allowlist, docs durability rule, and tests for + expected writeback tier. +3. Surface and approval safety: `surfaceAudience`, developer-only stop-gate + narrowing, short approval uniqueness / executability checks. +4. Operator comfort refinements: value heartbeat, teaching / architecture + explanation preference, timestamp standard, dirty-worktree reminder policy, + and command capability detection. + +Stop after each group if verification fails, if the change requires a new state +surface not listed in this plan, or if the implementation starts expanding into +release automation, frontend work, or project-specific behavior. + +## Implementation Steps + +### 1. Make full closeout human-first + +Tighten the output contract so full closeout starts with `一句话结论`, then the +phase result, concrete changes, solved problem, verification, and next phase +preview. Move `Threadsmith Decision` to the end as a compact protocol footer. + +Required behavior: + +- closeout must not open with a long protocol state list; +- `Threadsmith Decision` should contain only mode, source layer, role-chain + status, and stop condition; +- next phase preview must include why now, deliverables, done-when, and stop + condition; +- if multiple options are presented, one recommendation must be selected first + and the tradeoff must be explained in the required fields. + +Verification: + +- update `scripts/verify-threadsmith-skill-contract.mjs`; +- keep or add fixture coverage that rejects option-only next-step output; +- update any exact skeleton verifier or fixture that assumed + `Threadsmith Decision` appears first. + +### 2. Reduce truth writeback by default + +Clarify and enforce the difference between: + +- `evidence-only`: no committed project state mutation; +- `current-context`: update only the next-turn context or evidence needed to + avoid confusion; +- `committed-truth`: update phase / acceptance / status / packets only for + durable project state changes. + +Required behavior: + +- light repairs should not update multiple `.threadsmith` state files; +- normal implementation should update committed truth only at real acceptance, + blocker, failed verification, scope change, or recovery boundaries; +- closeout artifacts should not be created just because work completed; +- role packets should not be rewritten when their content would merely repeat + current packet facts. + +Writeback tier file allowlist: + +| Tier | Default `.threadsmith` writes | Allowed examples | Disallowed examples | +| --- | --- | --- | --- | +| `evidence-only` | 0 committed state files | final response, command output, test output, local run artifact outside committed truth | `.threadsmith/current-phase.json`, `.threadsmith/acceptance-state.json`, `.threadsmith/project-status.json`, role packets | +| `current-context` | only next-turn context / evidence when needed | `.threadsmith/context/current-packet.json`, `.threadsmith/context/evidence-summary.json`, `.threadsmith/active-work.json` when it affects the next action | project brief, roadmap, phase acceptance, provider routing, skill routing, phase history | +| `committed-truth` | durable project state only at real boundaries | `.threadsmith/current-phase.json`, `.threadsmith/acceptance-state.json`, `.threadsmith/project-status.json`, `.threadsmith/project-brief.json`, `.threadsmith/project-roadmap.json`, `.threadsmith/project-supervision.json`, `.threadsmith/context/role-packets/*.json`, `.threadsmith/history/phases.jsonl`, handoff / routing files when those surfaces actually changed | broad rewrites of unrelated truth files, role packet rewrites that only restate current packet facts | + +If a project lacks the listed optional context files, do not create them just to +satisfy a tier. Use the closest existing evidence surface, or keep the evidence +in the final response. + +For `evidence-only` local run artifacts, prefer ignored or temporary paths such +as `tmp/`, `.cache/`, or an existing project-specific ignored run directory. +The final response should state whether a temporary artifact was left behind +and whether it is safe to delete. +If whether the path is ignored is unknown, the final response must label it as +an `untracked artifact risk` instead of treating it as clean evidence. + +Verification: + +- add runtime tests for recommendation metadata and expected writeback tier; +- at minimum cover writeback tier, `surfaceAudience`, short approval safety, + and next-step concreteness; +- add contract text that states what files may change for each tier; +- if fs-bridge behavior changes, add focused fs-bridge tests. + +### 3. Narrow stop gates for developer-only surfaces + +Change the "new CLI / API / UI must pause" rule into a risk-based rule. + +Required behavior: + +- developer-only helpers inside an already approved scope may continue under + `normal-implementation` when semantics are clear and verification is focused; +- public/user-facing commands, compatibility-affecting APIs, product semantics, + provider defaults, credentials, release behavior, destructive actions, or + cross-agent state still require `full-governance` or operator review; +- the output must explain whether the surface is developer-only, internal, or + user/public-facing. + +Runtime recommendation metadata should include: + +- `surfaceAudience: "internal" | "developer" | "operator" | "user_public"` +- `workVisibility: "internal" | "developer_visible" | "operator_visible" | "user_visible"` + +Classification: + +- `internal`: implementation-only object with no direct operator entry. +- `developer`: helper for maintainers, tests, local scripts, diagnostics, or + repo maintenance inside an approved scope. +- `operator`: command, prompt, UI, or workflow surface used by the person + operating the project but not by public end users. +- `user_public`: external end-user behavior, public API / CLI, published docs, + compatibility surface, release behavior, or shared integration. + +Ambiguous surface audience defaults to `operator` or `user_public` based on the +highest plausible audience and should pause if semantics are unclear. + +`workVisibility` should not exceed the actual impact range implied by +`surfaceAudience` unless the recommendation explicitly explains the mismatch, +such as a developer tool that changes user-visible generated output. + +Operator surface rule: + +- `operator` surfaces may continue inside an already approved scope when they + are local, reversible, and do not change long-term workflow semantics. +- upgrade `operator` surfaces to `full-governance` when they change workflow + meaning, create a long-lived operator/public entry point, affect + compatibility, alter default behavior, or could reasonably be mistaken for a + public/user-facing surface. +- local developer harnesses can be `developer` when they are only maintainers' + verification tools; they become `operator` when they are a normal way for the + project owner to run, inspect, or steer the project. +- developer-only long-lived helpers may remain `normal-implementation` when + they are local, reversible, clearly non-public, and inside the approved scope. + +Verification: + +- add next-step fixtures for developer-only CLI helper vs public CLI command; +- ensure ambiguous surface defaults to pause or full governance. + +### 4. Strengthen short approval safety + +Keep the benefit of "同意" continuing the previous recommendation, but require +the inherited recommendation to be safe. + +Required behavior: + +- short approval may continue only when the previous recommendation is unique, + concrete, in scope, and executable; +- if the previous recommendation offered multiple options without a selected + recommendation, Threadsmith must ask for a choice or choose only when the + user's wording identifies one; +- if relevant repo evidence or committed truth changed since the recommendation, + Threadsmith must re-anchor before continuing; +- it must not repeat the same recommendation as a new answer when the safe + continuation path is clear. + +Relevant evidence means one of: + +- `.threadsmith/current-phase.json`, `.threadsmith/acceptance-state.json`, + relevant role packet, current packet, active work, or phase history changed; +- a source file named by the previous recommendation changed; +- the command output, failed verification evidence, or blocker that justified + the recommendation changed; +- git evidence shows new changes in files the inherited action would edit. + +Dirty worktree changes outside those surfaces should be summarized only when +they affect staging, commit safety, or likely file overlap. + +Verification: + +- add action contract checks for uniqueness and executability; +- add runtime/orchestrator tests if short approval routing is represented in + code. + +### 5. Make docs creation opt-in by durability + +Prevent "write a closeout doc" from becoming automatic ceremony. + +Required behavior: + +- create long-lived plan/report docs only for architecture decisions, + cross-thread handoff, public docs/release, product boundary decisions, + recovery evidence, or explicit user request; +- ordinary small work sessions should use final response, command output, + current context, or evidence summary instead; +- docs should say why they are durable if created. + +Verification: + +- add skill contract checks for durable-doc criteria; +- update closeout role contract to avoid default report creation for light + repair and normal implementation. + +### 6. Strengthen generic value heartbeat + +Make the value heartbeat visible enough to counter long governance runs without +hardcoding any one project. + +Required behavior: + +- after repeated governance-heavy or internal-only accepted sessions, + Threadsmith should recommend a lightweight value check at the next boundary; +- the heartbeat should ask whether the next local engineering step is still + highest value; +- project-specific wording may come from AGENTS.md, project brief, or + preferences; +- the heartbeat must not interrupt an accepted implementation path unless a + real stop gate appears. + +Deterministic v1 trigger: + +- trigger once after 3 consecutive `full-governance` or internal-only accepted + sessions without a value heartbeat; +- trigger only at the next phase boundary or work-session closeout; +- reset when the operator accepts, performs, or explicitly skips the heartbeat. + +Counter source: + +- derive from `.threadsmith/history/phases.jsonl` when phase history exists; +- otherwise derive from closeout evidence available in current context or + evidence summary; +- do not add a new durable counter file in v1 unless the existing phase history + and evidence surfaces cannot represent the trigger reliably. + +Closeout evidence required for a computable heartbeat: + +- `governanceMode`: `light-repair` / `normal-implementation` / + `full-governance` +- `surfaceAudience`: `internal` / `developer` / `operator` / `user_public` +- `workVisibility`: `internal` / `developer_visible` / `operator_visible` / + `user_visible` +- `valueHeartbeatShown`: `true` / `false` / `skipped` + +If these fields are missing from old closeouts, do not infer them from vague +text unless the answer is obvious. Treat the old entry as unknown and avoid +rewriting it solely for heartbeat accounting. + +Write these fields first to the phase history entry when phase history exists. +If no phase history exists, write them to the evidence summary. The final +response can display the same fields for the operator, but it is not the +long-term counter source. + +Verification: + +- add fixture coverage for internal-only consecutive sessions triggering a + value heartbeat; +- add preference tests for configurable heartbeat questions. + +### 7. Improve teaching and architecture translation + +Make Threadsmith inherit project-level teaching preferences while staying +generic. + +Required behavior: + +- dense terms must be translated the first time they appear; +- next-step answers should include the affected layer in plain language; +- closeout should say whether a change is user-visible, developer-facing, or + internal foundation; +- if AGENTS.md asks for teaching-style explanations, Threadsmith should honor + that in operator-facing output without turning every answer into a tutorial. + +Preference entry: + +- add or derive `operatorExplanationStyle: "concise" | "balanced" | "teaching" | "detailed"`; +- source priority: explicit project preferences, then AGENTS.md guidance, then + project brief / supervision, then Threadsmith default `balanced`; +- this preference changes explanation depth only. It must not change safety + gates, verification level, or truth writeback tier. +- `detailed` means explanation depth, not governance audit mode. It must not be + confused with closeout tier `audit` or `full-governance`. + +Verification: + +- add output fixtures with dense terms and expected capability translation; +- add contract checks that forbid term-only next-step descriptions. + +### 8. Normalize timestamps and dirty-worktree warnings + +Reduce avoidable confusion and anxiety from repeated operational warnings. + +Required behavior: + +- committed truth timestamps should use UTC ISO 8601 with milliseconds, + `YYYY-MM-DDTHH:mm:ss.SSSZ`, generated by the runtime/fs-bridge writeback layer + when Threadsmith writes durable state; +- apply the timestamp standard to new writes only. Legacy timestamps remain + valid unless the owning file is touched for another approved reason; +- do not bulk-rewrite historical `.threadsmith` files only to normalize + timestamps; +- closeout should not invent relative time when an exact timestamp is needed; +- operator-facing responses may display local time only as a secondary + explanation, not as committed truth; +- dirty worktree warnings should be prominent only before stage/commit, + destructive operations, broad edits, or files likely to overlap with user + changes; +- ordinary read-only status should summarize dirty state once, not repeat it + every turn. + +Verification: + +- add contract wording for timestamp source/format; +- add runtime or contract tests if timestamp generation is centralized. + +### 9. Detect command capability before suggesting automation + +Avoid suggesting Threadsmith automation commands that the target project cannot +actually run. + +Required behavior: + +- before recommending a command such as `npm run threadsmith:autopilot`, verify + that the package script or command exists in the target repo; +- if unavailable, describe the manual equivalent instead of presenting the + command as executable; +- command suggestions should identify whether they are repo-local, + Threadsmith-control-deck-local, or globally installed. + +Lookup order: + +1. target project scripts or binaries, such as target `package.json`, local + launcher scripts, or documented repo commands; +2. Threadsmith control-deck scripts only when the current working repo is + Threadsmith control deck or the command is explicitly described as external + tooling; +3. globally installed command only after checking availability with a safe + read-only command lookup. + +If a command is not available in the target project, say "manual equivalent" +and describe the role-chain action in plain language instead of pretending the +automation command can run there. + +If the current tool environment cannot safely check command availability, do +not recommend the command as executable. State that availability was not +verified and provide the manual equivalent instead. + +Verification: + +- add action contract checks for command capability detection; +- add runtime/orchestrator tests if command recommendation is modeled in code. + +## Acceptance Criteria + +- Full closeout opens with a human-readable result, not a long protocol field + list. +- `Threadsmith Decision` is compact and appears after the explanatory sections. +- Small repairs and developer-only helpers no longer imply broad committed + truth writeback. +- New public/user-facing semantics still trigger full governance or operator + review. +- Short approval inherits only unique, concrete, safe recommendations. +- Threadsmith does not create plan/report docs unless the artifact is durable + enough to justify future maintenance. +- Value heartbeat appears after repeated internal governance work, but does not + interrupt accepted implementation; heartbeat evidence is computable from + recorded `governanceMode`, `surfaceAudience`, `workVisibility`, and + `valueHeartbeatShown` fields. +- Next-step recommendations state the concrete work, affected layer, why now, + deliverable, done-when, and stop condition; they must not provide only a phase + name. +- Dense technical terms are translated into operator language. +- Timestamp and dirty-worktree guidance is less noisy and more precise. +- Automation commands are suggested only when available or clearly marked as + unavailable with a manual fallback. + +## Risks + +- Reducing writeback too aggressively could make recovery and cross-agent + handoff less reliable. +- Narrowing stop gates could accidentally allow public behavior changes to + proceed without sufficient review. +- Making output more natural could hide audit details that are still needed for + release, recovery, or cross-agent state. +- Adding too many new tests or fixtures could make the skill harder to change. + +Mitigations: + +- Keep `full-governance + committed-truth` for unsafe or ambiguous cases. +- Treat developer-only vs public/user-facing as an explicit classification. +- Preserve compact protocol footer for auditability. +- Add targeted regression fixtures instead of broad snapshot tests. + +## Done When + +- The skill contract, action contract, runtime contract, and role contract all + describe the lighter governance behavior consistently. +- Runtime recommendation metadata can represent the new stop-gate and writeback + distinctions. +- Regression fixtures cover human-first closeout, short approval safety, + developer-only surface handling, value heartbeat, and docs/writeback + restraint. +- Verification commands pass. +- If a durable closeout report is created, it records why the report is durable, + what changed, what did not change, and whether the global skill was + intentionally synced. +- If no durable closeout report is created, the final response plus evidence + summary or verification output is sufficient for this plan's closeout. diff --git a/packages/domain/src/preferences.test.ts b/packages/domain/src/preferences.test.ts index c8868b7..34feca2 100644 --- a/packages/domain/src/preferences.test.ts +++ b/packages/domain/src/preferences.test.ts @@ -3,7 +3,8 @@ import { createPreferences, createValueHeartbeatPreference, resolveContinuationBehavior, - resolveGovernanceIntensity + resolveGovernanceIntensity, + resolveOperatorExplanationStyle } from "./preferences.ts"; describe("resolveContinuationBehavior", () => { @@ -24,6 +25,12 @@ describe("resolveContinuationBehavior", () => { expect(resolved.resolved.continuationBehaviorSource).toBe("fallback"); expect(resolved.resolvedGovernance?.governanceIntensity).toBe("standard"); expect(resolved.resolvedGovernance?.governanceIntensitySource).toBe("fallback"); + expect(resolved.resolvedOperatorExplanation?.operatorExplanationStyle).toBe( + "balanced" + ); + expect( + resolved.resolvedOperatorExplanation?.operatorExplanationStyleSource + ).toBe("fallback"); expect(resolved.valueHeartbeat?.source).toBe("fallback"); expect(resolved.valueHeartbeat?.questions[0]).toContain("usable"); }); @@ -60,4 +67,36 @@ describe("resolveContinuationBehavior", () => { "Did the project move closer to the intended user experience?" ]); }); + + it("resolves operator explanation style from project before AGENTS.md and project brief", () => { + const resolved = resolveOperatorExplanationStyle( + "teaching", + "detailed", + "concise" + ); + + expect(resolved.operatorExplanationStyle).toBe("teaching"); + expect(resolved.operatorExplanationStyleSource).toBe("project-default"); + }); + + it("uses AGENTS.md explanation style before project brief defaults", () => { + const resolved = createPreferences( + "smart-continuation", + null, + "ask-every-time", + null, + null, + null, + null, + "teaching", + "concise" + ); + + expect(resolved.resolvedOperatorExplanation?.operatorExplanationStyle).toBe( + "teaching" + ); + expect( + resolved.resolvedOperatorExplanation?.operatorExplanationStyleSource + ).toBe("agents-md-default"); + }); }); diff --git a/packages/domain/src/preferences.ts b/packages/domain/src/preferences.ts index 88a37d2..3257b52 100644 --- a/packages/domain/src/preferences.ts +++ b/packages/domain/src/preferences.ts @@ -27,6 +27,20 @@ export const governanceIntensitySourceSchema = z.enum([ "fallback" ]); +export const operatorExplanationStyleSchema = z.enum([ + "concise", + "balanced", + "teaching", + "detailed" +]); + +export const operatorExplanationStyleSourceSchema = z.enum([ + "project-default", + "agents-md-default", + "project-brief-default", + "fallback" +]); + export const DEFAULT_VALUE_HEARTBEAT_QUESTIONS = [ "Did the project become more usable, understandable, reliable, or closer to its stated goal?", "Is the next engineering step still the highest-value direction?", @@ -41,6 +55,7 @@ export const valueHeartbeatPreferenceSchema = z.object({ export const storedPreferencesSchema = z.object({ continuationBehavior: continuationBehaviorSchema.optional(), governanceIntensity: governanceIntensitySchema.optional(), + operatorExplanationStyle: operatorExplanationStyleSchema.optional(), valueHeartbeatQuestions: z.array(z.string().min(1)).min(1).optional(), projectCharterGate: z.object({ declinedSetup: z.boolean().default(false), @@ -59,14 +74,30 @@ export const resolvedGovernanceIntensityPreferenceSchema = z.object({ governanceIntensitySource: governanceIntensitySourceSchema }); +export const resolvedOperatorExplanationStylePreferenceSchema = z.object({ + operatorExplanationStyle: operatorExplanationStyleSchema, + operatorExplanationStyleSource: operatorExplanationStyleSourceSchema +}); + export const preferencesSchema = z.object({ projectDefault: continuationBehaviorSchema.nullable(), globalDefault: continuationBehaviorSchema.nullable(), governanceIntensityDefault: governanceIntensitySchema.nullable().optional(), agentsMdGovernanceDefault: governanceIntensitySchema.nullable().optional(), + operatorExplanationStyleDefault: operatorExplanationStyleSchema + .nullable() + .optional(), + agentsMdOperatorExplanationStyleDefault: operatorExplanationStyleSchema + .nullable() + .optional(), + projectBriefOperatorExplanationStyleDefault: operatorExplanationStyleSchema + .nullable() + .optional(), valueHeartbeat: valueHeartbeatPreferenceSchema.optional(), resolved: resolvedContinuationPreferenceSchema, - resolvedGovernance: resolvedGovernanceIntensityPreferenceSchema.optional() + resolvedGovernance: resolvedGovernanceIntensityPreferenceSchema.optional(), + resolvedOperatorExplanation: + resolvedOperatorExplanationStylePreferenceSchema.optional() }); export type ContinuationBehavior = z.infer; @@ -78,6 +109,12 @@ export type ContinuationBehaviorSource = z.infer< export type GovernanceIntensitySource = z.infer< typeof governanceIntensitySourceSchema >; +export type OperatorExplanationStyle = z.infer< + typeof operatorExplanationStyleSchema +>; +export type OperatorExplanationStyleSource = z.infer< + typeof operatorExplanationStyleSourceSchema +>; export type ValueHeartbeatPreference = z.infer< typeof valueHeartbeatPreferenceSchema >; @@ -134,6 +171,39 @@ export function resolveGovernanceIntensity( }); } +export function resolveOperatorExplanationStyle( + projectDefault?: OperatorExplanationStyle | null, + agentsMdDefault?: OperatorExplanationStyle | null, + projectBriefDefault?: OperatorExplanationStyle | null, + fallback: OperatorExplanationStyle = "balanced" +) { + if (projectDefault) { + return resolvedOperatorExplanationStylePreferenceSchema.parse({ + operatorExplanationStyle: projectDefault, + operatorExplanationStyleSource: "project-default" + }); + } + + if (agentsMdDefault) { + return resolvedOperatorExplanationStylePreferenceSchema.parse({ + operatorExplanationStyle: agentsMdDefault, + operatorExplanationStyleSource: "agents-md-default" + }); + } + + if (projectBriefDefault) { + return resolvedOperatorExplanationStylePreferenceSchema.parse({ + operatorExplanationStyle: projectBriefDefault, + operatorExplanationStyleSource: "project-brief-default" + }); + } + + return resolvedOperatorExplanationStylePreferenceSchema.parse({ + operatorExplanationStyle: fallback, + operatorExplanationStyleSource: "fallback" + }); +} + export function createValueHeartbeatPreference( projectQuestions?: string[] | null ): ValueHeartbeatPreference { @@ -156,18 +226,31 @@ export function createPreferences( fallback: ContinuationBehavior = "ask-every-time", governanceIntensityDefault?: GovernanceIntensity | null, agentsMdGovernanceDefault?: GovernanceIntensity | null, - valueHeartbeatQuestions?: string[] | null + valueHeartbeatQuestions?: string[] | null, + operatorExplanationStyleDefault?: OperatorExplanationStyle | null, + agentsMdOperatorExplanationStyleDefault?: OperatorExplanationStyle | null, + projectBriefOperatorExplanationStyleDefault?: OperatorExplanationStyle | null ): Preferences { return preferencesSchema.parse({ projectDefault: projectDefault ?? null, globalDefault: globalDefault ?? null, governanceIntensityDefault: governanceIntensityDefault ?? null, agentsMdGovernanceDefault: agentsMdGovernanceDefault ?? null, + operatorExplanationStyleDefault: operatorExplanationStyleDefault ?? null, + agentsMdOperatorExplanationStyleDefault: + agentsMdOperatorExplanationStyleDefault ?? null, + projectBriefOperatorExplanationStyleDefault: + projectBriefOperatorExplanationStyleDefault ?? null, valueHeartbeat: createValueHeartbeatPreference(valueHeartbeatQuestions), resolved: resolveContinuationBehavior(projectDefault, globalDefault, fallback), resolvedGovernance: resolveGovernanceIntensity( governanceIntensityDefault, agentsMdGovernanceDefault + ), + resolvedOperatorExplanation: resolveOperatorExplanationStyle( + operatorExplanationStyleDefault, + agentsMdOperatorExplanationStyleDefault, + projectBriefOperatorExplanationStyleDefault ) }); } diff --git a/packages/runtime/fixtures/human-centered-output/full-governance-audit.json b/packages/runtime/fixtures/human-centered-output/full-governance-audit.json index 9f04ddd..d488145 100644 --- a/packages/runtime/fixtures/human-centered-output/full-governance-audit.json +++ b/packages/runtime/fixtures/human-centered-output/full-governance-audit.json @@ -9,7 +9,9 @@ "operatingMode": "full-governance", "writebackTier": "committed-truth", "verificationLevel": "release", - "outputBudget": "audit" + "outputBudget": "audit", + "surfaceAudience": "user_public", + "workVisibility": "user_visible" }, "capabilityMustContain": "高风险动作" } diff --git a/packages/runtime/fixtures/human-centered-output/light-repair.json b/packages/runtime/fixtures/human-centered-output/light-repair.json index 02964f6..1fdd578 100644 --- a/packages/runtime/fixtures/human-centered-output/light-repair.json +++ b/packages/runtime/fixtures/human-centered-output/light-repair.json @@ -10,7 +10,9 @@ "operatingMode": "light-repair", "writebackTier": "evidence-only", "verificationLevel": "narrow", - "outputBudget": "lite" + "outputBudget": "lite", + "surfaceAudience": "internal", + "workVisibility": "internal" }, "capabilityMustContain": "小修复" } diff --git a/packages/runtime/fixtures/human-centered-output/unsafe-legacy-fallback.json b/packages/runtime/fixtures/human-centered-output/unsafe-legacy-fallback.json index 36db217..a3359d5 100644 --- a/packages/runtime/fixtures/human-centered-output/unsafe-legacy-fallback.json +++ b/packages/runtime/fixtures/human-centered-output/unsafe-legacy-fallback.json @@ -10,7 +10,9 @@ "operatingMode": "full-governance", "writebackTier": "committed-truth", "verificationLevel": "release", - "outputBudget": "audit" + "outputBudget": "audit", + "surfaceAudience": "operator", + "workVisibility": "operator_visible" }, "capabilityMustContain": "旧项目缺字段" } diff --git a/packages/runtime/fixtures/human-centered-output/value-heartbeat.json b/packages/runtime/fixtures/human-centered-output/value-heartbeat.json index 61478b2..ebaa926 100644 --- a/packages/runtime/fixtures/human-centered-output/value-heartbeat.json +++ b/packages/runtime/fixtures/human-centered-output/value-heartbeat.json @@ -10,6 +10,8 @@ "writebackTier": "current-context", "verificationLevel": "standard", "outputBudget": "standard", + "surfaceAudience": "operator", + "workVisibility": "operator_visible", "nextStepKind": "value-heartbeat" }, "capabilityMustContain": "连续治理" diff --git a/packages/runtime/fixtures/human-centered-output/what-next-normal.json b/packages/runtime/fixtures/human-centered-output/what-next-normal.json index 5194427..2d59fad 100644 --- a/packages/runtime/fixtures/human-centered-output/what-next-normal.json +++ b/packages/runtime/fixtures/human-centered-output/what-next-normal.json @@ -7,6 +7,8 @@ "writebackTier": "current-context", "verificationLevel": "standard", "outputBudget": "standard", + "surfaceAudience": "internal", + "workVisibility": "internal", "nextStepKind": "work-session-continue" }, "capabilityMustContain": "当前已接受" diff --git a/packages/runtime/src/humanCenteredOutput.test.ts b/packages/runtime/src/humanCenteredOutput.test.ts index 3939380..4e3593b 100644 --- a/packages/runtime/src/humanCenteredOutput.test.ts +++ b/packages/runtime/src/humanCenteredOutput.test.ts @@ -15,6 +15,8 @@ interface Fixture { writebackTier: string; verificationLevel: string; outputBudget: string; + surfaceAudience?: string; + workVisibility?: string; nextStepKind?: string; }; capabilityMustContain: string; diff --git a/packages/runtime/src/nextBestStep.test.ts b/packages/runtime/src/nextBestStep.test.ts index e447fe1..c0ef181 100644 --- a/packages/runtime/src/nextBestStep.test.ts +++ b/packages/runtime/src/nextBestStep.test.ts @@ -634,6 +634,86 @@ describe("selectNextBestStep", () => { expect(result.primary.reason).toContain("consumer surface"); }); + it("lets developer-only helper surfaces continue inside the approved scope", () => { + const result = selectNextBestStep( + baseState, + undefined, + null, + undefined, + undefined, + null, + { + surfaceAudience: "developer" + } + ); + + expect(result.primary.actionId).toBe("advance-phase"); + expect(result.primary.operatingMode).toBe("normal-implementation"); + expect(result.primary.writebackTier).toBe("current-context"); + expect(result.primary.surfaceAudience).toBe("developer"); + expect(result.primary.workVisibility).toBe("developer_visible"); + }); + + it("lets local operator surfaces continue when they do not change workflow semantics", () => { + const result = selectNextBestStep( + baseState, + undefined, + null, + undefined, + undefined, + null, + { + surfaceAudience: "operator" + } + ); + + expect(result.primary.actionId).toBe("advance-phase"); + expect(result.primary.operatingMode).toBe("normal-implementation"); + expect(result.primary.surfaceAudience).toBe("operator"); + expect(result.primary.workVisibility).toBe("operator_visible"); + }); + + it("stops when an operator surface changes long-term workflow semantics", () => { + const result = selectNextBestStep( + baseState, + undefined, + null, + undefined, + undefined, + null, + { + surfaceAudience: "operator", + createsLongLivedOperatorOrPublicEntry: true + } + ); + + expect(result.primary.actionId).toBe("open-current-phase"); + expect(result.primary.operatingMode).toBe("full-governance"); + expect(result.primary.surfaceAudience).toBe("operator"); + expect(result.primary.workVisibility).toBe("operator_visible"); + expect(result.primary.reason).toContain("operator surface"); + }); + + it("stops for user-public surfaces even without an older consumer-surface flag", () => { + const result = selectNextBestStep( + baseState, + undefined, + null, + undefined, + undefined, + null, + { + surfaceAudience: "user_public" + } + ); + + expect(result.primary.actionId).toBe("open-current-phase"); + expect(result.primary.operatingMode).toBe("full-governance"); + expect(result.primary.surfaceAudience).toBe("user_public"); + expect(result.primary.workVisibility).toBe("user_visible"); + expect(result.primary.reason).toContain("user/public surface"); + }); + it("recommends a value heartbeat after three consecutive governance-heavy closeouts", () => { const result = selectNextBestStep( baseState, diff --git a/packages/runtime/src/nextBestStep.ts b/packages/runtime/src/nextBestStep.ts index 13867f6..a0b2273 100644 --- a/packages/runtime/src/nextBestStep.ts +++ b/packages/runtime/src/nextBestStep.ts @@ -17,6 +17,8 @@ export type { OperatingMode, WritebackTier, RuntimeVerificationLevel, + SurfaceAudience, + WorkVisibility, NextBestStepDecision, RuntimeActionId } from "./nextBestStepModel.ts"; @@ -30,12 +32,45 @@ export interface AdaptiveWorkSessionSignals { auditStopReason?: string; introducesConsumerSurface?: boolean; changesProductSemantics?: boolean; + surfaceAudience?: SurfaceAudience; + workVisibility?: WorkVisibility; + changesWorkflowSemantics?: boolean; + createsLongLivedOperatorOrPublicEntry?: boolean; + changesDefaultBehavior?: boolean; + compatibilityRisk?: boolean; + publicMisuseRisk?: boolean; lightRepair?: boolean; lightRepairReason?: string; legacyMetadataMissing?: boolean; legacyMetadataSafe?: boolean; } +function defaultVisibilityForAudience( + surfaceAudience: SurfaceAudience +): WorkVisibility { + switch (surfaceAudience) { + case "developer": + return "developer_visible"; + case "operator": + return "operator_visible"; + case "user_public": + return "user_visible"; + case "internal": + default: + return "internal"; + } +} + +function resolveSurfaceMetadata(signals: AdaptiveWorkSessionSignals = {}) { + const surfaceAudience = signals.surfaceAudience ?? "internal"; + + return { + surfaceAudience, + workVisibility: + signals.workVisibility ?? defaultVisibilityForAudience(surfaceAudience) + }; +} + function appendIfMissing(base: string, fragment: string) { return base.includes(fragment) ? base : `${base} ${fragment}`.trim(); } @@ -77,7 +112,14 @@ function isAuditStopRequired(signals: AdaptiveWorkSessionSignals) { return Boolean( signals.requiresAuditStop || signals.introducesConsumerSurface || - signals.changesProductSemantics + signals.changesProductSemantics || + signals.surfaceAudience === "user_public" || + (signals.surfaceAudience === "operator" && + (signals.changesWorkflowSemantics || + signals.createsLongLivedOperatorOrPublicEntry || + signals.changesDefaultBehavior || + signals.compatibilityRisk || + signals.publicMisuseRisk)) ); } @@ -94,6 +136,14 @@ function auditStopReason(signals: AdaptiveWorkSessionSignals) { return "下一步会改变 product semantics,必须先停在 phase 边界确认。"; } + if (signals.surfaceAudience === "user_public") { + return "下一步会影响 user/public surface,必须先确认兼容性、验收和 stop condition。"; + } + + if (signals.surfaceAudience === "operator") { + return "下一步会改变 operator surface 的长期用法或默认行为,必须先确认 workflow 语义。"; + } + return "下一步触发 audit stop gate,必须先确认边界。"; } @@ -118,23 +168,35 @@ function canContinueWorkSession(state: ProjectState, pendingUserDecision: unknow ); } -function normalImplementationMetadata(capabilityTranslation: string) { +function normalImplementationMetadata( + capabilityTranslation: string, + surfaceAudience: SurfaceAudience = "internal", + workVisibility: WorkVisibility = defaultVisibilityForAudience(surfaceAudience) +) { return { operatingMode: "normal-implementation" as const, writebackTier: "current-context" as const, verificationLevel: "standard" as const, outputBudget: "standard" as const, + surfaceAudience, + workVisibility, affectedLayer: "runtime recommendation", capabilityTranslation }; } -function fullGovernanceMetadata(capabilityTranslation: string) { +function fullGovernanceMetadata( + capabilityTranslation: string, + audience: SurfaceAudience = "operator", + visibility: WorkVisibility = "operator_visible" +) { return { operatingMode: "full-governance" as const, writebackTier: "committed-truth" as const, verificationLevel: "release" as const, outputBudget: "audit" as const, + surfaceAudience: audience, + workVisibility: visibility, affectedLayer: "governance boundary", capabilityTranslation }; @@ -146,11 +208,44 @@ function lightRepairMetadata(capabilityTranslation: string) { writebackTier: "evidence-only" as const, verificationLevel: "narrow" as const, outputBudget: "lite" as const, + surfaceAudience: "internal" as const, + workVisibility: "internal" as const, affectedLayer: "focused repair", capabilityTranslation }; } +function auditStopMetadata( + signals: AdaptiveWorkSessionSignals, + capabilityTranslation: string +) { + if (signals.introducesConsumerSurface || signals.changesProductSemantics) { + return fullGovernanceMetadata( + capabilityTranslation, + "user_public", + "user_visible" + ); + } + + if (signals.surfaceAudience === "user_public") { + return fullGovernanceMetadata( + capabilityTranslation, + "user_public", + signals.workVisibility ?? "user_visible" + ); + } + + if (signals.surfaceAudience === "operator") { + return fullGovernanceMetadata( + capabilityTranslation, + "operator", + signals.workVisibility ?? "operator_visible" + ); + } + + return fullGovernanceMetadata(capabilityTranslation); +} + function isBootstrapDecisionStage( state: ProjectState, latestRun: AgentRunRecord | null, @@ -609,7 +704,8 @@ export function selectNextBestStep( `${auditStopReason(adaptiveSignals)} 这类动作不能降级成轻量 work session,需要先确认 phase contract、验收和 stop condition。`, ["planner"], "audit 边界已经确认,或者该动作被拆成安全的后续 phase。", - fullGovernanceMetadata( + auditStopMetadata( + adaptiveSignals, "高风险动作先回到审计边界,确保不会把公共行为、发布或状态风险混进普通实现。" ) ), @@ -734,7 +830,9 @@ export function selectNextBestStep( { nextStepKind: "value-heartbeat", ...normalImplementationMetadata( - "连续治理后先确认项目价值方向,避免一直做内部工程而忘记用户或操作者能感受到什么。" + "连续治理后先确认项目价值方向,避免一直做内部工程而忘记用户或操作者能感受到什么。", + "operator", + "operator_visible" ) } ), @@ -763,6 +861,8 @@ export function selectNextBestStep( }; } + const surfaceMetadata = resolveSurfaceMetadata(adaptiveSignals); + return { primary: recommendation( "advance-phase", @@ -787,7 +887,9 @@ export function selectNextBestStep( ? { nextStepKind: "work-session-continue", ...normalImplementationMetadata( - "当前已接受的实现链可以继续推进到自然停点,而不是重新包装成新阶段。" + "当前已接受的实现链可以继续推进到自然停点,而不是重新包装成新阶段。", + surfaceMetadata.surfaceAudience, + surfaceMetadata.workVisibility ) } : undefined diff --git a/packages/runtime/src/nextBestStepModel.ts b/packages/runtime/src/nextBestStepModel.ts index 44e726c..86c617a 100644 --- a/packages/runtime/src/nextBestStepModel.ts +++ b/packages/runtime/src/nextBestStepModel.ts @@ -26,12 +26,26 @@ export type RuntimeVerificationLevel = "narrow" | "standard" | "release"; export type OutputBudget = "lite" | "standard" | "audit"; +export type SurfaceAudience = + | "internal" + | "developer" + | "operator" + | "user_public"; + +export type WorkVisibility = + | "internal" + | "developer_visible" + | "operator_visible" + | "user_visible"; + export interface RecommendationMetadata { nextStepKind?: NextStepKind; operatingMode?: OperatingMode; writebackTier?: WritebackTier; verificationLevel?: RuntimeVerificationLevel; outputBudget?: OutputBudget; + surfaceAudience?: SurfaceAudience; + workVisibility?: WorkVisibility; affectedLayer?: string; capabilityTranslation?: string; } @@ -43,6 +57,8 @@ export interface ActionRecommendation { writebackTier?: WritebackTier; verificationLevel?: RuntimeVerificationLevel; outputBudget?: OutputBudget; + surfaceAudience?: SurfaceAudience; + workVisibility?: WorkVisibility; affectedLayer?: string; capabilityTranslation?: string; label: string; diff --git a/scripts/verify-threadsmith-skill-contract.mjs b/scripts/verify-threadsmith-skill-contract.mjs index 4501f1e..157549c 100644 --- a/scripts/verify-threadsmith-skill-contract.mjs +++ b/scripts/verify-threadsmith-skill-contract.mjs @@ -70,7 +70,13 @@ const checks = [ /### Truth Writeback Tiers/, /`evidence-only`/, /`current-context`/, - /`committed-truth`/ + /`committed-truth`/, + /Writeback file allowlist/, + /untracked artifact risk/, + /Do not create optional context files/, + /Runtime recommendations should include surface metadata/, + /surfaceAudience/, + /workVisibility/ ] }, { @@ -118,10 +124,13 @@ const checks = [ /当前 stop reason/, /Compact sync output/, /Conceptual answer/, + /human-first order/, + /### 一句话结论/, /### 本 phase 的结果/, /### 这一步具体做了什么/, /### 下一 phase 预览/, - /### 你需要审核的点/ + /### 你需要审核的点/, + /### Threadsmith Decision/ ] }, { @@ -130,9 +139,7 @@ const checks = [ patterns: [ /render this exact skeleton/, /do not omit required labels/, - /project state:/, - /current phase state:/, - /acceptance state:/, + /先用 1-2 句中文说明/, /phase 名称:/, /交付物:/, /结果一句话:/, @@ -150,7 +157,10 @@ const checks = [ /Questions/, /Deliverables/, /Non-goals/, - /Done when/ + /Done when/, + /Do not output only `Option A` \/ `Option B`/, + /Keep this section last and compact/, + /Do not include long project summaries/ ] }, { @@ -161,7 +171,10 @@ const checks = [ /Every dense technical noun/, /技术名词:它在人话里意味着/, /capability-first phrasing/, - /Do not let filenames, enum values, command names, or internal function names be the main explanation/ + /Do not let filenames, enum values, command names, or internal function names be the main explanation/, + /operatorExplanationStyle/, + /new-write-only UTC ISO 8601/, + /manual\s+equivalent/ ] }, { @@ -186,8 +199,9 @@ const checks = [ /已用 Threadsmith 推进并完成/, /That shape loses the operator orientation contract/, /overrides ordinary concise final-answer style/, - /exact field skeleton/, + /human-first field skeleton/, /free-form paragraphs only/, + /一句话结论/, /phase 名称/, /结果一句话/, /架构影响/, @@ -199,7 +213,12 @@ const checks = [ /架构\/流程缺口/, /未运行与风险/, /planner-style brief/, - /what changed in this phase/ + /what changed in this phase/, + /Do not output only `Option A` \/ `Option B`/, + /Threadsmith Decision.*compact.*at the end/s, + /operatorExplanationStyle/, + /new-write-only UTC ISO 8601/, + /manual equivalent/ ] }, { @@ -294,6 +313,8 @@ const checks = [ /`evidence-only`/, /`current-context`/, /`committed-truth`/, + /Writeback file allowlist/, + /untracked artifact risk/, /## Output Budget Rule/, /## Capability Translation Rule/ ] @@ -318,7 +339,10 @@ const checks = [ /does not claim durable phase\s+acceptance/, /verification type: unit, contract, smoke, e2e, behavior sample, or structural\s+mock evidence/, /mock-first evidence/, - /capability translation/ + /capability translation/, + /Writeback tier guard/, + /creating closeout reports by default/, + /if a durable report is created/ ] }, { @@ -489,9 +513,20 @@ const checks = [ /## Human-Centered Runtime Metadata/, /operating mode: `light-repair`, `normal-implementation`, or\s+`full-governance`/, /writeback tier: `evidence-only`, `current-context`, or `committed-truth`/, + /surface audience: `internal`, `developer`, `operator`, or `user_public`/, + /work visibility: `internal`, `developer_visible`, `operator_visible`, or\s+`user_visible`/, + /heartbeat-compatible closeout evidence/, + /valueHeartbeatShown/, + /phase history as the long-term counter source/, /Missing legacy metadata/, /full-governance` \+ `committed-truth`/, - /Evidence-only actions must not mutate project state files/ + /Evidence-only actions must not mutate project state files/, + /Writeback file allowlist/, + /untracked artifact\s+risk/s, + /Operator comfort metadata/, + /operatorExplanationStyle/, + /Timestamp rule/, + /new-write-only UTC ISO 8601/ ] }, {