@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.
- package/README.md +1 -1
- package/package.json +1 -1
- package/skills/workit-bdd/SKILL.md +35 -29
- package/skills/workit-continue/SKILL.md +53 -0
- package/skills/workit-debug/SKILL.md +41 -36
- package/skills/workit-deslop/SKILL.md +35 -25
- package/skills/workit-fanout/SKILL.md +62 -0
- package/skills/workit-fanout/references/brief.md +56 -0
- package/skills/workit-implement/SKILL.md +48 -44
- package/skills/workit-review/SKILL.md +42 -51
- package/skills/workit-review/references/impact.md +24 -0
- package/skills/workit-shape/SKILL.md +71 -0
- package/skills/workit-shape/references/diagrams.md +17 -0
- package/skills/workit-shape/references/knowledge.md +58 -0
- package/skills/workit-shape/references/mockups.md +15 -0
- package/skills/workit-shape/references/slicing.md +42 -0
- package/skills/workit-ship/SKILL.md +52 -0
- package/skills/workit-test-audit/SKILL.md +10 -2
- package/skills/workit-verify-app/SKILL.md +63 -0
- package/skills/workit-verify-app/references/template.md +49 -0
- package/src/autonomy.ts +309 -44
- package/src/core/context-read.ts +144 -0
- package/src/core/methods.ts +71 -98
- package/src/core/skill-manifests.ts +49 -25
- package/src/core/store-lock.ts +1 -1
- package/src/core/task-context.ts +1 -1
- package/src/core/task-contract.ts +8 -130
- package/src/core/task-engine.ts +57 -879
- package/src/core/task-evaluation.ts +63 -5
- package/src/core/task-store.ts +2 -107
- package/src/core/workers.ts +12 -158
- package/src/core/workspaces.ts +15 -0
- package/src/core/youtrack-writes.ts +92 -0
- package/src/core.ts +2 -79
- package/src/forge/pr-ops.ts +2 -2
- package/src/hooks/handle.ts +29 -5
- package/src/hooks/hosts/claude-code.ts +0 -8
- package/src/hooks/hosts/codex.ts +2 -11
- package/src/hooks/hosts/cursor.ts +0 -8
- package/src/hooks/hosts/opencode.ts +0 -8
- package/src/hooks/hosts/pi.ts +0 -10
- package/src/ledger.ts +19 -10
- package/templates/plan-template.md +17 -18
- package/templates/spec-template.md +4 -3
- package/templates/workit-contract.md +3 -2
- package/skills/workit-babysit/SKILL.md +0 -37
- package/skills/workit-behavioral-tdd/SKILL.md +0 -56
- package/skills/workit-blast-radius/SKILL.md +0 -26
- package/skills/workit-challenge/SKILL.md +0 -47
- package/skills/workit-diagram/SKILL.md +0 -27
- package/skills/workit-green-run/SKILL.md +0 -24
- package/skills/workit-handoff/SKILL.md +0 -37
- package/skills/workit-mockup/SKILL.md +0 -23
- package/skills/workit-plan/SKILL.md +0 -45
- package/skills/workit-steer/SKILL.md +0 -39
- package/src/core/authority.ts +0 -1033
- package/src/core/auto-approval.ts +0 -338
- package/src/core/external-action-effects.ts +0 -2441
- package/src/core/external-action.ts +0 -1204
- package/templates/execution-contract.md +0 -40
package/src/core/methods.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { Assurance, Capability, Dimension, Policy } from "./task-contract";
|
|
2
2
|
|
|
3
3
|
export type MethodId =
|
|
4
|
-
| "workit-
|
|
5
|
-
| "workit-
|
|
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-
|
|
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-
|
|
20
|
-
"workit-
|
|
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
|
-
|
|
23
|
-
"workit-implement": {
|
|
24
|
-
|
|
25
|
-
//
|
|
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-
|
|
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
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
132
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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-
|
|
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-
|
|
12
|
-
"workit-
|
|
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-
|
|
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-
|
|
33
|
-
"wk-
|
|
34
|
-
"wk-
|
|
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
|
-
|
|
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)
|
package/src/core/store-lock.ts
CHANGED
|
@@ -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();
|
package/src/core/task-context.ts
CHANGED
|
@@ -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.
|
|
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 {
|