@brainervirus/workit-core 4.0.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/skills/workit-bdd/SKILL.md +35 -29
  4. package/skills/workit-continue/SKILL.md +53 -0
  5. package/skills/workit-debug/SKILL.md +41 -36
  6. package/skills/workit-deslop/SKILL.md +35 -25
  7. package/skills/workit-fanout/SKILL.md +62 -0
  8. package/skills/workit-fanout/references/brief.md +56 -0
  9. package/skills/workit-implement/SKILL.md +48 -44
  10. package/skills/workit-review/SKILL.md +42 -51
  11. package/skills/workit-review/references/impact.md +24 -0
  12. package/skills/workit-shape/SKILL.md +71 -0
  13. package/skills/workit-shape/references/diagrams.md +17 -0
  14. package/skills/workit-shape/references/knowledge.md +58 -0
  15. package/skills/workit-shape/references/mockups.md +15 -0
  16. package/skills/workit-shape/references/slicing.md +42 -0
  17. package/skills/workit-ship/SKILL.md +52 -0
  18. package/skills/workit-test-audit/SKILL.md +10 -2
  19. package/skills/workit-verify-app/SKILL.md +63 -0
  20. package/skills/workit-verify-app/references/template.md +49 -0
  21. package/src/autonomy.ts +309 -44
  22. package/src/core/context-read.ts +144 -0
  23. package/src/core/methods.ts +71 -98
  24. package/src/core/skill-manifests.ts +49 -25
  25. package/src/core/store-lock.ts +1 -1
  26. package/src/core/task-context.ts +1 -1
  27. package/src/core/task-contract.ts +8 -130
  28. package/src/core/task-engine.ts +57 -879
  29. package/src/core/task-evaluation.ts +63 -5
  30. package/src/core/task-store.ts +2 -107
  31. package/src/core/workers.ts +12 -158
  32. package/src/core/workspaces.ts +15 -0
  33. package/src/core/youtrack-writes.ts +92 -0
  34. package/src/core.ts +2 -79
  35. package/src/forge/pr-ops.ts +2 -2
  36. package/src/hooks/handle.ts +29 -5
  37. package/src/hooks/hosts/claude-code.ts +0 -8
  38. package/src/hooks/hosts/codex.ts +2 -11
  39. package/src/hooks/hosts/cursor.ts +0 -8
  40. package/src/hooks/hosts/opencode.ts +0 -8
  41. package/src/hooks/hosts/pi.ts +0 -10
  42. package/src/ledger.ts +19 -10
  43. package/templates/plan-template.md +17 -18
  44. package/templates/spec-template.md +4 -3
  45. package/templates/workit-contract.md +3 -2
  46. package/skills/workit-babysit/SKILL.md +0 -37
  47. package/skills/workit-behavioral-tdd/SKILL.md +0 -56
  48. package/skills/workit-blast-radius/SKILL.md +0 -26
  49. package/skills/workit-challenge/SKILL.md +0 -47
  50. package/skills/workit-diagram/SKILL.md +0 -27
  51. package/skills/workit-green-run/SKILL.md +0 -24
  52. package/skills/workit-handoff/SKILL.md +0 -37
  53. package/skills/workit-mockup/SKILL.md +0 -23
  54. package/skills/workit-plan/SKILL.md +0 -45
  55. package/skills/workit-steer/SKILL.md +0 -39
  56. package/src/core/authority.ts +0 -1033
  57. package/src/core/auto-approval.ts +0 -338
  58. package/src/core/external-action-effects.ts +0 -2441
  59. package/src/core/external-action.ts +0 -1204
  60. package/templates/execution-contract.md +0 -40
@@ -1,13 +1,13 @@
1
1
  import type { Assurance, Capability, Dimension, Policy } from "./task-contract";
2
2
 
3
3
  export type MethodId =
4
- | "workit-challenge"
5
- | "workit-behavioral-tdd"
4
+ | "workit-shape"
5
+ | "workit-bdd"
6
6
  | "workit-review"
7
- | "workit-plan"
8
7
  | "workit-implement"
8
+ | "workit-fanout"
9
9
  | "workit-debug"
10
- | "workit-handoff"
10
+ | "workit-continue"
11
11
  | "workit-deslop";
12
12
 
13
13
  type MethodDefinition = {
@@ -16,15 +16,16 @@ type MethodDefinition = {
16
16
  };
17
17
 
18
18
  export const METHODS: Readonly<Record<MethodId, MethodDefinition>> = {
19
- "workit-challenge": { dimensions: ["challenge", "decisions"] },
20
- "workit-behavioral-tdd": { dimensions: ["testing"], ruleIds: ["mechanical-existing-checks"] },
19
+ "workit-shape": { dimensions: ["challenge", "decisions", "artifacts", "continuity"] },
20
+ "workit-bdd": { dimensions: ["testing"] },
21
21
  "workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review", "self-review"] },
22
- "workit-plan": { dimensions: ["artifacts", "continuity"] },
23
- "workit-implement": { dimensions: ["delegation"] },
24
- // workit-debug and workit-handoff stay slash-invoked: no generated rule
25
- // routes to them, so they never appear spuriously in method selection.
22
+ // Mechanical work needs only the existing checks, not a RED/GREEN ritual.
23
+ "workit-implement": { ruleIds: ["mechanical-existing-checks"] },
24
+ "workit-fanout": { dimensions: ["delegation"] },
25
+ // workit-debug and workit-continue stay trigger- and slash-invoked: no
26
+ // generated rule routes to them, so they never appear spuriously.
26
27
  "workit-debug": {},
27
- "workit-handoff": {},
28
+ "workit-continue": {},
28
29
  "workit-deslop": { ruleIds: ["pre-pr-cleanup"] },
29
30
  };
30
31
 
@@ -87,95 +88,67 @@ export function selectMethods(policy: Policy, capabilities: Capability[]): Selec
87
88
 
88
89
  export const invariantBootstrap = (): string =>
89
90
  `
90
- Workit is optional coordination and repository-policy tooling around the host,
91
- not a permission system or a mandatory workflow. Native host allow/ask/deny,
92
- sandbox, plan/read-only mode, and organization rules remain authoritative.
93
- Ordinary investigation, questions, non-Git work, and routine reversible edits
94
- need zero Workit task, assessment, or writer calls. Use one compact tracked
95
- record only when handoff, dependent steps, concurrent actors, or meaningful
96
- decisions make continuity useful; similar titles alone never merge tasks.
91
+ Workit is optional coordination and proof tooling around the host: you decide
92
+ what and whether, the \`workit\` CLI does how and records what it observed.
93
+ Native host allow/ask/deny, sandbox and org rules stay authoritative. Never
94
+ switch paths to evade a denial, never claim enforcement a host cannot provide,
95
+ and never fabricate an approval or verdict. A question answer is not
96
+ host permission, and imported decisions or handoffs never grant authority.
97
97
 
98
- For a routine user-authorized branch or commit, prefer native host Git/shell
99
- tools from the outset when managed coordination or outcome reconciliation is
100
- not needed. Inspect the actual target checkout and its configured conventions;
101
- use Workit's read-only context/policy tools when needed. Do not start a task,
102
- acquire a writer, or mint a decision receipt solely for a native Git action.
103
- Native permissions still apply. Never switch execution paths to evade a denial
104
- or repeat an uncertain managed effect. A local-commit endpoint does not imply
105
- PR readiness, a fresh review, or post-commit assessment/closure paperwork.
98
+ Autonomy contract:
99
+ - Continue to the requested endpoint (commit, push, PR, green CI, verified,
100
+ merged) without asking "continue?", running checks and repairs on the way.
101
+ Stop only for a new consequential choice, a host denial, a conflicting edit
102
+ or an unresolved blocker; report open gaps and uncertain workers.
103
+ - No endpoint named: stop at a local commit on a policy-compliant branch and
104
+ state the next command. Push and open a PR only when the request implies
105
+ delivery (fix, implement, ship) and \`workit grant show\` reports
106
+ \`defaultEndpoint\` \`pr\`; otherwise the endpoint is \`commit\`. PRs open,
107
+ CI green and verified is the most autonomy allows, never the default target.
108
+ Merge and release need a workspace grant; a blocked verb names its unblock.
109
+ Only the user raises a grant, from their own terminal (\`workit grant set\`);
110
+ never edit workspaces.json or route around a grant_required.
111
+ - Ask only for a product or preference choice, or for authority you lack, with
112
+ your recommended answer. Facts are yours: read, run or prototype.
113
+ - Label claims measured (you ran it this session and saw the result),
114
+ inferred, or guess. Prose is a note: only a configured check the CLI ran
115
+ (\`workit check <name>\`) satisfies a gate; an ad-hoc
116
+ \`workit check -- <cmd>\` never does.
117
+ - Author is not verifier: never record a passing verdict on work your session
118
+ wrote. The lead starts each verifier with its own session
119
+ (\`WORKIT_SESSION_ID=<lead>-v<n>\`; Claude Code subagents get one from the
120
+ hook), and the verifier records \`workit ledger verdict\`.
121
+ - A local-commit endpoint does not imply PR readiness, and a local commit alone
122
+ is not evidence of a requested remote push. Before reporting delivery,
123
+ reconcile every requested item and observe it (\`workit verify-delivery\`).
106
124
 
107
- For tracked work, inspect its current state and use the shared operations for
108
- task, policy, evidence, finding, decision, worker, writer, and state changes.
109
- Each branch has one implicit task: an operation without taskId applies to it,
110
- and the first recording (a note, check, finding or evidence) creates it, so you
111
- never manage task ids. Start a record only for an explicit tracked objective;
112
- assess or reassess only
113
- when policy selection or changed evidence/constraints requires it. Omitted
114
- expectedRevision and expectedWorkspaceRevision use current values; explicit
115
- values are still concurrency-checked, so never copy revisions between calls.
116
- A busy result means another live Workit call holds the checkout lock: retry the
117
- same call; it is not a recovery condition. A lock left by a dead process is
118
- reclaimed on the next write, and \`workit doctor --fix-lock\` clears it on demand.
119
- An omitted revision absorbs a concurrent write: Workit re-reads, re-checks
120
- policy, and reapplies the call, returning busy under persistent contention. A
121
- revision_conflict means a revision you passed is stale: re-read the record
122
- before deciding whether to retry.
123
- A solo edit does not need writer acquisition; use it when concurrent checkout
124
- writers need coordination. Record only observed facts and checks. Close-time
125
- testing and verification gates accept only a configured check the CLI ran:
126
- \`workit check <name>\` (\`npx -y @brainervirus/workit-cli check <name>\` when
127
- \`workit\` is not on PATH); a recorded check result is a note and an ad-hoc
128
- \`workit check -- <cmd>\` never satisfies a gate. Evidence can become stale when its bound candidate or tree changes; reconcile findings against the
129
- current candidate before recording completion.
125
+ CLI first: if a step has one right answer, use the \`workit\` verb instead of
126
+ hand-running git, gh or glab: check, git, pr, ci, stack, verify-delivery,
127
+ ledger, handoff, test-audit (\`workit help <verb>\`; without \`workit\` on
128
+ PATH, \`npx -y @brainervirus/workit-cli\`). \`busy\` is retryable
129
+ (\`workit doctor --fix-lock\` clears a dead lock); \`blocked\` names its unblock.
130
+ The task families (task, policy, evidence, finding, decision, worker, state)
131
+ are optional continuity for tracked work; each branch has one implicit task
132
+ that the first recording creates, and a solo edit needs no task. A decision
133
+ records a user choice; it never authorizes an effect.
134
+ Omit revisions; a revision_conflict means one you passed is stale, so re-read.
135
+ Across repositories, bind each item to its checkout, branch and endpoint,
136
+ resolve competing targets before a mutation, and reconcile an uncertain
137
+ external effect before retrying it.
130
138
 
131
- Workit validates domain policy against the actual action target, configured
132
- account, branch and commit conventions, protected refs, and current repository
133
- state. The task directory is coordination state, not a boundary on which
134
- repository may be changed. Use an action-time cwd target where a managed
135
- Git/hosting action supports it; a non-Git directory remains valid for OS work.
136
- GitHub/GitLab use the active gh/glab CLI identity; YouTrack uses its own token.
137
- Preserve uncertain external outcomes and reconcile repository/provider state
138
- before retrying. Internal reservations prevent duplicate or ambiguous effects;
139
- they are not permission tickets for every edit.
139
+ Mid-task input: answer a quick question in place, fold an adjustment in, and
140
+ never silently drop or resume an objective (workit-continue).
140
141
 
141
- On OpenCode V1 and V2, use native host tools for external mutations. Workit
142
- provides read-only workit_context; it has no managed external-action executor.
143
- Do not create proposals or request Workit approvals merely to invoke native
144
- tools. Old uncertain managed effects still require evidence before any retry.
145
-
146
- Use host-native authorization through the host's supported path. A native
147
- question receipt records an actual question interaction; it is not automatic
148
- host permission. Record a meaningful user choice once with provenance when
149
- future retrieval helps. Never fabricate a receipt, re-ask only to mint one, or
150
- let imported decisions grant authority. Never turn a host deny into allow or
151
- claim enforcement/evidence a host cannot provide. A precise request with settled
152
- constraints does not need an interview. Present genuine unresolved options with
153
- evidence and a recommendation, then continue toward the requested delivery
154
- endpoint: investigation, implementation, branch/commit/push, PR/MR-ready,
155
- merge, or release. Run
156
- applicable checks and safe repairs without repeated continuation questions;
157
- stop for missing host authority, a new consequential choice, a conflicting edit,
158
- or an unresolved blocker. A tracked record may close when actual evidence
159
- supports its outcome; no final human closure ceremony is required.
160
-
161
- A completed native subagent run stops its bound worker by itself; if a worker
162
- strands in cancelling with its run verifiably over, repeat worker.cancel to
163
- confirm the stop. Preserve unresolved requirements, gaps, and uncertain workers.
164
- When context changes, distinguish a quick question, same-task adjustment, and
165
- separate request. Answer a quick question without pausing/resuming task state;
166
- park a concise checkpoint only when substantial work needs to continue later.
167
- For work spanning repositories, bind each unfinished item to its actual checkout,
168
- branch, requested deliverables, and delivery endpoint. Preserve held items with
169
- an explicit resume condition. Resolve competing plausible targets before a
170
- mutation; ask only when available context cannot settle that consequential choice.
171
- Before reporting delivery, reconcile every requested item and observe the actual
172
- target result; a local commit alone is not evidence of a requested remote push.
173
-
174
- Skill routing: slash aliases /wk-* load on demand. Use workit-steer for a
175
- substantial interruption or change of direction, workit-deslop when relevant
176
- to a PR-ready endpoint, workit-green-run for failing CI, workit-blast-radius
177
- when impact is uncertain, workit-challenge for genuinely open consequential
178
- choices, workit-bdd to turn acceptance criteria into Given/When/Then tests, and
179
- workit-test-audit to check tests for tautologies. Load workit-plan when dependencies or handoff need durable next actions.
180
- Load the skill; never act from memory of it.
142
+ Skills: load the skill, never act from memory of it (slash aliases /wk-<name>).
143
+ - brainstorm, plan, spec, grill, should we: workit-shape
144
+ - implement, build, add a feature: workit-implement
145
+ - review, blast radius: workit-review
146
+ - bug, broken, flaky, regression: workit-debug
147
+ - ship, babysit, CI, merge: workit-ship
148
+ - resume, pick up, handoff, interruption: workit-continue
149
+ - BDD, TDD, acceptance criteria, Given/When/Then: workit-bdd
150
+ - test audit, tautology, weak tests: workit-test-audit
151
+ - deslop, slop, dead code: workit-deslop
152
+ - fan out, parallelize, parallel agents, swarm: workit-fanout
153
+ - verify the app, smoke test, prove it works: workit-verify-app
181
154
  `.trim();
@@ -2,44 +2,68 @@ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
4
  export const WORKIT_METHOD_SKILLS = [
5
- "workit-challenge",
6
- "workit-behavioral-tdd",
7
- "workit-review",
8
- "workit-plan",
5
+ "workit-shape",
9
6
  "workit-implement",
7
+ "workit-review",
10
8
  "workit-debug",
11
- "workit-handoff",
12
- "workit-babysit",
13
- "workit-blast-radius",
14
- "workit-deslop",
15
- "workit-diagram",
16
- "workit-mockup",
17
- "workit-green-run",
18
- "workit-steer",
9
+ "workit-ship",
10
+ "workit-continue",
19
11
  "workit-bdd",
20
12
  "workit-test-audit",
13
+ "workit-deslop",
14
+ "workit-fanout",
15
+ "workit-verify-app",
21
16
  ] as const;
22
17
 
18
+ export type WorkitSkill = (typeof WORKIT_METHOD_SKILLS)[number];
19
+
23
20
  /** wk- slash aliases (one per skill): alias → method skill. An alias routes
24
21
  * through policy to model skills; an alias never calls another alias. */
25
22
  export const WORKIT_SKILL_ALIASES = {
26
- "wk-challenge": "workit-challenge",
27
- "wk-babysit": "workit-babysit",
23
+ "wk-shape": "workit-shape",
28
24
  "wk-implement": "workit-implement",
29
- "wk-plan": "workit-plan",
30
- "wk-debug": "workit-debug",
31
25
  "wk-review": "workit-review",
32
- "wk-handoff": "workit-handoff",
33
- "wk-tdd": "workit-behavioral-tdd",
34
- "wk-blast-radius": "workit-blast-radius",
35
- "wk-deslop": "workit-deslop",
36
- "wk-diagram": "workit-diagram",
37
- "wk-mockup": "workit-mockup",
38
- "wk-green-run": "workit-green-run",
39
- "wk-steer": "workit-steer",
26
+ "wk-debug": "workit-debug",
27
+ "wk-ship": "workit-ship",
28
+ "wk-continue": "workit-continue",
40
29
  "wk-bdd": "workit-bdd",
41
30
  "wk-test-audit": "workit-test-audit",
42
- } as const;
31
+ "wk-deslop": "workit-deslop",
32
+ "wk-fanout": "workit-fanout",
33
+ "wk-verify-app": "workit-verify-app",
34
+ } as const satisfies Record<string, WorkitSkill>;
35
+
36
+ /**
37
+ * The words a user types that should load each skill. Each skill's
38
+ * description and its bootstrap routing line carry every one of them, and no
39
+ * two skills share one (checked by test), so a trigger routes to one skill.
40
+ */
41
+ export const WORKIT_SKILL_TRIGGERS: Readonly<Record<WorkitSkill, readonly string[]>> = {
42
+ "workit-shape": ["brainstorm", "plan", "spec", "grill", "should we"],
43
+ "workit-implement": ["implement", "build", "add a feature"],
44
+ "workit-review": ["review", "blast radius"],
45
+ "workit-debug": ["bug", "broken", "flaky", "regression"],
46
+ "workit-ship": ["ship", "babysit", "CI", "merge"],
47
+ "workit-continue": ["resume", "pick up", "handoff", "interruption"],
48
+ "workit-bdd": ["BDD", "TDD", "acceptance criteria", "Given/When/Then"],
49
+ "workit-test-audit": ["test audit", "tautology", "weak tests"],
50
+ "workit-deslop": ["deslop", "slop", "dead code"],
51
+ "workit-fanout": ["fan out", "parallelize", "parallel agents", "swarm"],
52
+ "workit-verify-app": ["verify the app", "smoke test", "prove it works"],
53
+ };
54
+
55
+ /** The `description:` frontmatter value of a SKILL.md (one line, unquoted). */
56
+ export const skillDescription = (skillMd: string): string => {
57
+ const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---/.exec(skillMd)?.[1] ?? "";
58
+ const line = /^description:\s*(.+)$/m.exec(frontmatter)?.[1] ?? "";
59
+ return line.trim().replace(/^(["'])(.*)\1$/, "$2");
60
+ };
61
+
62
+ /** A generated Cursor slash command for one alias; `commands/` is committed
63
+ * because Cursor discovers the plugin from git, so the build writes these and
64
+ * a drift test compares them to the committed copies. */
65
+ export const cursorCommandText = (alias: string, skill: string, description: string): string =>
66
+ `# /${alias}\n\nLoad and apply the bundled \`${skill}\` skill. ${description}\n\nExtra context: $ARGUMENTS\n`;
43
67
 
44
68
  export const skillManifestNames = (root: string): string[] =>
45
69
  existsSync(root)
@@ -20,7 +20,6 @@ export type MetadataLock = {
20
20
  processStart: string | null;
21
21
  host: string;
22
22
  nonce: string;
23
- externalAction?: true;
24
23
  };
25
24
 
26
25
  const metadataLockSchema = z
@@ -29,6 +28,7 @@ const metadataLockSchema = z
29
28
  processStart: z.string().nullable(),
30
29
  host: z.string().min(1),
31
30
  nonce: z.string().min(1),
31
+ /** Written by ≤4.x managed external actions; read and ignored (D17). */
32
32
  externalAction: z.literal(true).optional(),
33
33
  })
34
34
  .strict();
@@ -109,7 +109,7 @@ const compactTimestamp = (value: Utc): string =>
109
109
  `${value.slice(0, 19)}.${value.slice(20, -1).padEnd(9, "0")}`;
110
110
  const compactDecisionChoice = (entry: TaskView["task"]["decisions"][number]): string | null => {
111
111
  const stated = entry.data.binding.statedChoice?.text;
112
- const approved = entry.data.response === "approved" ? entry.data.binding.approvedContent : "";
112
+ const approved = entry.data.response === "approved" ? entry.data.binding.presented : "";
113
113
  const source = stated?.trim() || approved.trim() || null;
114
114
  return compactText(source, COMPACT_TEXT_BYTES);
115
115
  };
@@ -10,7 +10,6 @@ export const OPERATION_FAMILIES = [
10
10
  "finding",
11
11
  "decision",
12
12
  "worker",
13
- "writer",
14
13
  "state",
15
14
  ] as const;
16
15
  export type OperationFamily = (typeof OPERATION_FAMILIES)[number];
@@ -145,19 +144,6 @@ export const refSchema = z.discriminatedUnion("kind", [
145
144
  .strict(),
146
145
  z.object({ kind: z.literal("host"), host: hostSchema, handle: nonEmpty }).strict(),
147
146
  z.object({ kind: z.literal("external"), url: text.url() }).strict(),
148
- // Standing auto-approval receipt: authorizes without a live question while
149
- // the referenced workspace rule covers the operation class. Verified live
150
- // at reserve time against current config; dropped on export/import (a rule
151
- // from another machine must never authorize here). Scalar-only shape keeps
152
- // advertised operation schemas within provider depth limits.
153
- z
154
- .object({
155
- kind: z.literal("standing"),
156
- workspace: nonEmpty,
157
- class: nonEmpty,
158
- configDigest: nullableDigest,
159
- })
160
- .strict(),
161
147
  ]);
162
148
  export type Ref = z.infer<typeof refSchema>;
163
149
 
@@ -274,7 +260,6 @@ export const provenanceSchema = z
274
260
  host: hostSchema,
275
261
  session: refSchema.nullable(),
276
262
  workerId: nullableId,
277
- receipts: z.array(refSchema),
278
263
  })
279
264
  .strict();
280
265
  export type Provenance = z.infer<typeof provenanceSchema>;
@@ -622,6 +607,13 @@ export const evidenceEvaluationSchema = z
622
607
  })
623
608
  .strict();
624
609
  export type EvidenceEvaluation = z.infer<typeof evidenceEvaluationSchema>;
610
+ /**
611
+ * A durable decision record (D2, D18): agent-asserted like every other
612
+ * recording. It satisfies `decisions` requirements and accepted limitations;
613
+ * it never authorizes an external effect — autonomy grants and the host's
614
+ * own permission prompt do that. Legacy keys (`approvedContent`, `displayed`,
615
+ * `standing`, `consumption`) are dropped on read (D17).
616
+ */
625
617
  export const decisionSchema = z
626
618
  .object({
627
619
  purpose: z.enum(["design", "action", "limitation", "preference"]),
@@ -631,53 +623,18 @@ export const decisionSchema = z
631
623
  workspaceId: id,
632
624
  scope: scopeSchema,
633
625
  presented: text,
634
- approvedContent: text,
635
- displayed: text.optional(),
636
626
  contentRefs: z.array(refSchema),
637
627
  statedChoice: z.object({ ref: text, text: text }).strict().optional(),
638
- standing: z.object({ workspace: nonEmpty, class: nonEmpty }).strict().optional(),
639
628
  })
640
629
  .strict(),
641
630
  digest,
642
631
  response: z.enum(["approved", "rejected", "stated"]),
643
632
  requirementIds: z.array(digest),
644
633
  revoked: z.object({ at: utc, reason: text }).strict().nullable(),
645
- consumption: z
646
- .object({
647
- state: z.enum(["reserved", "consumed", "uncertain"]),
648
- at: utc,
649
- actionRef: refSchema,
650
- /** Historical identifier only; never interpreted as native authority. */
651
- historicalActionId: text.optional(),
652
- })
653
- .strict()
654
- .nullable(),
655
634
  })
656
635
  .strict();
657
636
  export type Decision = z.infer<typeof decisionSchema>;
658
637
 
659
- /** Show-before-ask guard: binding questions are single-sentence and never
660
- * carry the artifact, descriptor, or payload body in the question UI. */
661
- export const BINDING_QUESTION_BUDGET = 300;
662
- const bindingQuestionHeader = /^Workit decision: (design|action|limitation|preference)$/;
663
- export const workitBindingQuestionIssue = (questions: unknown): string | null => {
664
- if (!Array.isArray(questions) || questions.length !== 1) return null;
665
- const question = questions[0] as { header?: unknown; question?: unknown; options?: unknown };
666
- if (!question || typeof question !== "object") return null;
667
- if (typeof question.header !== "string" || !bindingQuestionHeader.test(question.header.trim()))
668
- return null;
669
- const over = (label: string, value: string) =>
670
- `invalid_input: ${label} is ${value.length} characters; Workit binding questions must stay within ${BINDING_QUESTION_BUDGET}. present the item in the conversation first, then ask a short scoped question.`;
671
- if (typeof question.question === "string" && question.question.length > BINDING_QUESTION_BUDGET)
672
- return over("the question text", question.question);
673
- if (Array.isArray(question.options))
674
- for (const option of question.options) {
675
- const description = (option as { description?: unknown }).description;
676
- if (typeof description === "string" && description.length > BINDING_QUESTION_BUDGET)
677
- return over("an option description", description);
678
- }
679
- return null;
680
- };
681
638
  export const findingSchema = z
682
639
  .object({
683
640
  claim: text,
@@ -724,14 +681,6 @@ export const workerSchema = z
724
681
  })
725
682
  .strict();
726
683
  export type Worker = z.infer<typeof workerSchema>;
727
- export const ownerSchema = z
728
- .object({
729
- taskId: id,
730
- workerId: nullableId,
731
- session: z.object({ kind: z.literal("host"), host: hostSchema, handle: nonEmpty }).strict(),
732
- })
733
- .strict();
734
- export type Owner = z.infer<typeof ownerSchema>;
735
684
  export const closureSchema = z
736
685
  .object({
737
686
  outcome: outcomeSchema,
@@ -742,37 +691,6 @@ export const closureSchema = z
742
691
  })
743
692
  .strict();
744
693
  export type Closure = z.infer<typeof closureSchema>;
745
- export const actionProgressSchema = z
746
- .object({
747
- decisionId: id,
748
- steps: z.array(nonEmpty),
749
- completedSteps: z.array(nonEmpty),
750
- })
751
- .strict()
752
- .check((ctx) => {
753
- const { steps, completedSteps } = ctx.value;
754
- const valid =
755
- new Set(steps).size === steps.length &&
756
- new Set(completedSteps).size === completedSteps.length &&
757
- completedSteps.every((step, index) => steps[index] === step);
758
- if (!valid)
759
- ctx.issues.push({
760
- code: "custom",
761
- input: ctx.value,
762
- message: "completed action steps must be a unique prefix of approved steps",
763
- path: ["completedSteps"],
764
- });
765
- });
766
- export type ActionProgress = z.infer<typeof actionProgressSchema>;
767
- export const actionProgressListSchema = z.array(actionProgressSchema).check((ctx) => {
768
- if (new Set(ctx.value.map((progress) => progress.decisionId)).size !== ctx.value.length)
769
- ctx.issues.push({
770
- code: "custom",
771
- input: ctx.value,
772
- message: "action progress decision ids must be unique",
773
- path: ["decisionId"],
774
- });
775
- });
776
694
  export const runtimeSchema = z
777
695
  .object({
778
696
  createdWith: text.nullable(),
@@ -802,7 +720,6 @@ export const taskRecordSchema = z
802
720
  candidates: z.array(candidateSchema),
803
721
  evidence: z.array(entrySchema(evidenceSchema)),
804
722
  decisions: z.array(entrySchema(decisionSchema)),
805
- actionProgress: actionProgressListSchema.optional(),
806
723
  findings: z.array(entrySchema(findingSchema)),
807
724
  workers: z.array(entrySchema(workerSchema)),
808
725
  /** Paths a reader must understand; see parseStoredRecord. */
@@ -917,10 +834,6 @@ export const workspaceRecordSchema = z
917
834
  revision,
918
835
  root: nonEmpty,
919
836
  runtime: runtimeSchema.optional(),
920
- writer: z
921
- .object({ state: z.enum(["held", "uncertain"]), owner: ownerSchema, acquiredAt: utc })
922
- .strict()
923
- .nullable(),
924
837
  /** Paths a reader must understand; see parseStoredRecord. */
925
838
  critical: z.array(nonEmpty).optional(),
926
839
  })
@@ -970,7 +883,6 @@ export const taskSummarySchema = z
970
883
  progress: progressSchema,
971
884
  policy: policySchema.nullable(),
972
885
  requirements: z.array(requirementEvaluationSchema),
973
- writer: workspaceRecordSchema.shape.writer,
974
886
  })
975
887
  .strict();
976
888
  export type TaskSummary = z.infer<typeof taskSummarySchema>;
@@ -1046,7 +958,6 @@ const taskOperations = {
1046
958
  ...taskId,
1047
959
  ...revisions,
1048
960
  expectedWorkspaceRevision: revision.optional(),
1049
- authorityRefs: z.array(refSchema).optional(),
1050
961
  }),
1051
962
  close: operation({
1052
963
  action: z.literal("close"),
@@ -1141,30 +1052,12 @@ const workerOperations = {
1141
1052
  reason: text,
1142
1053
  }),
1143
1054
  };
1144
- const writerOperations = {
1145
- acquire: operation({
1146
- action: z.literal("acquire"),
1147
- ...taskId,
1148
- expectedRevision: revision.optional(),
1149
- expectedWorkspaceRevision: revision.optional(),
1150
- workerId: nullableId.optional(),
1151
- reason: text.optional(),
1152
- }),
1153
- release: operation({
1154
- action: z.literal("release"),
1155
- ...taskId,
1156
- expectedRevision: revision.optional(),
1157
- expectedWorkspaceRevision: revision.optional(),
1158
- reason: text.optional(),
1159
- }),
1160
- };
1161
1055
  const stateOperations = {
1162
1056
  export: operation({ action: z.literal("export"), ...taskId }),
1163
1057
  import: operation({
1164
1058
  action: z.literal("import"),
1165
1059
  expectedWorkspaceRevision: revision.nullable().optional(),
1166
1060
  bundle: exportBundleSchema,
1167
- authorityRefs: z.array(refSchema),
1168
1061
  }),
1169
1062
  };
1170
1063
 
@@ -1175,7 +1068,6 @@ export const operationSchemas = {
1175
1068
  finding: z.discriminatedUnion("action", Object.values(findingOperations) as any),
1176
1069
  decision: z.discriminatedUnion("action", Object.values(decisionOperations) as any),
1177
1070
  worker: z.discriminatedUnion("action", Object.values(workerOperations) as any),
1178
- writer: z.discriminatedUnion("action", Object.values(writerOperations) as any),
1179
1071
  state: z.discriminatedUnion("action", Object.values(stateOperations) as any),
1180
1072
  } as const;
1181
1073
  export type OperationRequest = z.infer<(typeof operationSchemas)[OperationFamily]>;
@@ -1198,7 +1090,6 @@ export const advertisedOperationSchemas = {
1198
1090
  finding: advertised(findingOperations),
1199
1091
  decision: advertised(decisionOperations),
1200
1092
  worker: advertised(workerOperations),
1201
- writer: advertised(writerOperations),
1202
1093
  state: advertised(stateOperations),
1203
1094
  } as const;
1204
1095
  export type TaskStartRequest = z.infer<typeof taskOperations.start>;
@@ -1222,7 +1113,6 @@ export type ErrorCode =
1222
1113
  | "permission_denied"
1223
1114
  | "capability_unavailable"
1224
1115
  | "requirements_unsatisfied"
1225
- | "writer_conflict"
1226
1116
  /** Retryable: another live Workit call holds the checkout's metadata lock. */
1227
1117
  | "busy"
1228
1118
  | "recovery_required"
@@ -1237,16 +1127,9 @@ export type ErrorDetails = {
1237
1127
  actualWorkspaceRevision?: Revision | null;
1238
1128
  requirementIds?: Digest[];
1239
1129
  capability?: string;
1240
- owner?: Owner;
1241
1130
  path?: string;
1242
1131
  operation?: string;
1243
1132
  outcome?: "not_started" | "pending" | "unknown";
1244
- /** Present when a native approval must be asked from a concise proposal. */
1245
- proposal?: {
1246
- presented: string;
1247
- approvedContent: string;
1248
- descriptorDigest: string;
1249
- };
1250
1133
  guidance?: string;
1251
1134
  /** Structured remedy for unsatisfied requirements: rule, reason, and
1252
1135
  * satisfaction text instead of opaque requirement hashes alone. */
@@ -1505,12 +1388,7 @@ const compareNullableText = (left: string | null, right: string | null): number
1505
1388
  const compareBooleanNullable = (left: boolean | null, right: boolean | null): number =>
1506
1389
  left === right ? 0 : left === null ? -1 : right === null ? 1 : Number(left) - Number(right);
1507
1390
  export function decisionDigest(input: Omit<Decision, "digest"> | Decision): Digest {
1508
- const {
1509
- digest: _ignored,
1510
- revoked: _revoked,
1511
- consumption: _consumption,
1512
- ...value
1513
- } = input as Decision;
1391
+ const { digest: _ignored, revoked: _revoked, ...value } = input as Decision;
1514
1392
  return sha256(value);
1515
1393
  }
1516
1394
  export function candidateDigest(input: Candidate): Digest {