@brainervirus/workit-core 6.0.1 → 7.1.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/package.json +1 -1
- package/skills/workit-deslop/SKILL.md +1 -1
- package/skills/workit-implement/SKILL.md +9 -4
- package/skills/workit-review/SKILL.md +2 -0
- package/skills/workit-shape/SKILL.md +1 -1
- package/skills/workit-ship/SKILL.md +1 -1
- package/src/autonomy.ts +24 -0
- package/src/core/branch.ts +63 -6
- package/src/core/config.ts +9 -3
- package/src/core/methods.ts +7 -5
- package/src/core/operation-input.ts +506 -0
- package/src/core/policy/derive.ts +338 -0
- package/src/core/policy/judgment.ts +192 -0
- package/src/core/policy-resolver.ts +32 -427
- package/src/core/pr-create.ts +8 -3
- package/src/core/release-tracks.ts +779 -0
- package/src/core/task-contract.ts +40 -229
- package/src/core/task-engine.ts +51 -58
- package/src/core/task-evaluation.ts +35 -1
- package/src/core/vcs-config.ts +65 -16
- package/src/core/workspaces.ts +82 -16
- package/src/core.ts +8 -6
- package/src/forge/pr-ops.ts +31 -0
- package/src/git/ops.ts +61 -13
- package/src/hooks/handle.ts +19 -4
- package/src/hooks/hosts/claude-code.ts +20 -1
- package/src/hooks/hosts/codex.ts +3 -0
- package/src/hooks/hosts/cursor.ts +11 -2
- package/src/hooks/hosts/fields.ts +35 -0
- package/src/hooks/hosts/opencode.ts +1 -0
- package/src/hooks/hosts/pi.ts +1 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/protocol.ts +3 -0
- package/src/hooks/write-gate.ts +303 -0
- package/src/ledger.ts +20 -0
package/package.json
CHANGED
|
@@ -22,7 +22,7 @@ behavior. A change that wants new behavior is its own change.
|
|
|
22
22
|
validator that changes behavior is not deslop.
|
|
23
23
|
5. **Re-run the checks** and report lines removed, not lines written. Nothing
|
|
24
24
|
to clean is a valid result: say what you checked ("0 removals; ran knip and
|
|
25
|
-
read the diff"). When a tracked task lists a `pre-pr-cleanup` requirement,
|
|
25
|
+
read the diff"). When a tracked task lists a `pre-pr-cleanup` requirement (≤6.x tasks),
|
|
26
26
|
record this result as its evidence.
|
|
27
27
|
|
|
28
28
|
## Example
|
|
@@ -11,6 +11,9 @@ description: Build a requested change in small verified steps - follow local pat
|
|
|
11
11
|
neighbour that already does something similar. Copy its patterns, names and
|
|
12
12
|
error handling. Repo rules (AGENTS.md, CLAUDE.md, lint config) win.
|
|
13
13
|
2. On the default branch? Branch first: `workit git branch --kind feature --slug <s>`.
|
|
14
|
+
Tracking it? Judge once: `workit policy assess --judge risk=normal
|
|
15
|
+
behavior=yes product-choice=no plan=no` (trivial and no for a mechanical
|
|
16
|
+
fix); Workit derives what close needs.
|
|
14
17
|
3. Small steps that each leave the tree green. Behavior change: write the
|
|
15
18
|
acceptance as Given/When/Then and see a test fail first (workit-bdd).
|
|
16
19
|
Mechanical change: the existing checks are enough.
|
|
@@ -24,10 +27,12 @@ description: Build a requested change in small verified steps - follow local pat
|
|
|
24
27
|
PR (`workit git push`, `workit pr create --fill`, then workit-ship) only when
|
|
25
28
|
that was requested, or the request implies delivery and `workit grant show`
|
|
26
29
|
reports `defaultEndpoint` `pr`; otherwise the endpoint is `commit`.
|
|
27
|
-
7.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
7. Verify. Normal risk: after `workit check test` passes, record your own
|
|
31
|
+
`workit ledger verdict tests-verified --self --how "<what you ran>"`; it
|
|
32
|
+
reads self-reviewed, never verified. High risk, a workspace with
|
|
33
|
+
`verification: independent` (`workit grant show`) or a verified merge needs
|
|
34
|
+
a fresh verifier with its own session (`WORKIT_SESSION_ID=<yours>-v1`;
|
|
35
|
+
Claude Code: the `verifier` agent). Before saying done, reconcile
|
|
31
36
|
every named deliverable against the target checkout and observe it (for a push:
|
|
32
37
|
`workit verify-delivery push`).
|
|
33
38
|
|
|
@@ -8,6 +8,8 @@ description: Independent review of a diff, branch or PR - intent fidelity and st
|
|
|
8
8
|
Review the candidate, never the author's summary of it. A session that wrote a
|
|
9
9
|
commit on the branch cannot record a passing verdict: if that is you, hand the
|
|
10
10
|
review to a fresh agent (Claude Code: the `reviewer` or `verifier` agent).
|
|
11
|
+
An author's `--self` verdict reads self-reviewed and never counts as
|
|
12
|
+
independent; `type-check-only` never proves a behavior change.
|
|
11
13
|
|
|
12
14
|
1. Pin the candidate: `git rev-parse HEAD`, the base, `git diff <base>...HEAD`;
|
|
13
15
|
for a PR, `workit pr status --json` (checks, unresolved threads).
|
|
@@ -43,7 +43,7 @@ a choice that is hard to reverse, surprising and a real trade-off; a glossary
|
|
|
43
43
|
entry for a term you had to resolve; `.out-of-scope/<concept>.md` for a rejected
|
|
44
44
|
request that will come back. A one-file mechanical fix gets none. Formats and
|
|
45
45
|
triggers: `references/knowledge.md`. Record each settled choice once:
|
|
46
|
-
`workit ledger decision "<what>" --why "<why>"
|
|
46
|
+
`workit ledger decision "<what>" --why "<why>"`; cite a plan: `workit policy assess --ref <path>`.
|
|
47
47
|
|
|
48
48
|
## 5. Slice as tracer bullets
|
|
49
49
|
|
|
@@ -33,7 +33,7 @@ merge: Stop at PR-ready unless the user set merge as the endpoint.
|
|
|
33
33
|
`workit ci rerun --failed --reason flake` (once per head). Real: reproduce
|
|
34
34
|
with `workit check`, fix the root cause, batch fixes into one push.
|
|
35
35
|
6. **Verified.** After the last push a non-author records a verdict
|
|
36
|
-
(workit-review). Land only when granted: `workit stack land` (the
|
|
36
|
+
(workit-review); `pr status` showing self-reviewed is not verified. Land only when granted: `workit stack land` (the
|
|
37
37
|
contiguous verified run from the root) or `workit pr merge`.
|
|
38
38
|
7. **Observe it landed:** `workit verify-delivery pr` or `merge`.
|
|
39
39
|
|
package/src/autonomy.ts
CHANGED
|
@@ -92,6 +92,19 @@ export const DEFAULT_GRANTS: Readonly<Grants> = Object.freeze({
|
|
|
92
92
|
*/
|
|
93
93
|
export type DefaultEndpoint = "commit" | "pr";
|
|
94
94
|
export const DEFAULT_ENDPOINT: DefaultEndpoint = "commit";
|
|
95
|
+
/**
|
|
96
|
+
* How a normal-risk behavior change is verified (S17). `self` (default): an
|
|
97
|
+
* observed passing `workit check test` plus the author's own `--self`
|
|
98
|
+
* verdict, labelled self-reviewed. `independent`: a verdict from a session
|
|
99
|
+
* that did not author the branch. User config only, like grants.
|
|
100
|
+
*/
|
|
101
|
+
export type VerificationMode = "self" | "independent";
|
|
102
|
+
export const DEFAULT_VERIFICATION: VerificationMode = "self";
|
|
103
|
+
export const verificationOf = (entry: unknown): VerificationMode =>
|
|
104
|
+
(entry as { verification?: unknown } | null)?.verification === "independent"
|
|
105
|
+
? "independent"
|
|
106
|
+
: "self";
|
|
107
|
+
|
|
95
108
|
const endpointOf = (entry: unknown): DefaultEndpoint =>
|
|
96
109
|
(entry as { defaultEndpoint?: unknown } | null)?.defaultEndpoint === "pr" ? "pr" : "commit";
|
|
97
110
|
|
|
@@ -106,6 +119,7 @@ export type Autonomy = {
|
|
|
106
119
|
source: AutonomySource;
|
|
107
120
|
accountConfigured: boolean;
|
|
108
121
|
defaultEndpoint: DefaultEndpoint;
|
|
122
|
+
verification: VerificationMode;
|
|
109
123
|
/** Set when the grants file was not read (see grantsOverride). */
|
|
110
124
|
note?: string;
|
|
111
125
|
};
|
|
@@ -182,6 +196,7 @@ export function resolveAutonomy(cwd: string): Autonomy {
|
|
|
182
196
|
source: "default",
|
|
183
197
|
accountConfigured: false,
|
|
184
198
|
defaultEndpoint: DEFAULT_ENDPOINT,
|
|
199
|
+
verification: DEFAULT_VERIFICATION,
|
|
185
200
|
note: override,
|
|
186
201
|
};
|
|
187
202
|
const workspace = resolveWorkspaceFrom(
|
|
@@ -197,6 +212,7 @@ export function resolveAutonomy(cwd: string): Autonomy {
|
|
|
197
212
|
source,
|
|
198
213
|
accountConfigured: Boolean(workspace?.vcs?.account),
|
|
199
214
|
defaultEndpoint: endpointOf(workspace),
|
|
215
|
+
verification: verificationOf(workspace),
|
|
200
216
|
};
|
|
201
217
|
}
|
|
202
218
|
|
|
@@ -282,6 +298,7 @@ export type GrantWrite =
|
|
|
282
298
|
grants: Grants;
|
|
283
299
|
configured: GrantKind[];
|
|
284
300
|
defaultEndpoint: DefaultEndpoint;
|
|
301
|
+
verification: VerificationMode;
|
|
285
302
|
}
|
|
286
303
|
| { ok: false; code: "not_found" | "invalid_input" | "failed"; error: string };
|
|
287
304
|
|
|
@@ -298,6 +315,8 @@ export function writeGrants(
|
|
|
298
315
|
dir: string = grantsDir(),
|
|
299
316
|
/** A new default endpoint; `null` removes it (back to `commit`). */
|
|
300
317
|
endpoint?: DefaultEndpoint | null,
|
|
318
|
+
/** A new verification mode; `null` removes it (back to `self`). */
|
|
319
|
+
verification?: VerificationMode | null,
|
|
301
320
|
): GrantWrite {
|
|
302
321
|
const current = readWorkspacesResult(dir);
|
|
303
322
|
if (current.status === "missing")
|
|
@@ -329,6 +348,8 @@ export function writeGrants(
|
|
|
329
348
|
delete entry.autoApprove;
|
|
330
349
|
if (endpoint === null || endpoint === DEFAULT_ENDPOINT) delete entry.defaultEndpoint;
|
|
331
350
|
else if (endpoint) entry.defaultEndpoint = endpoint;
|
|
351
|
+
if (verification === null || verification === DEFAULT_VERIFICATION) delete entry.verification;
|
|
352
|
+
else if (verification) entry.verification = verification;
|
|
332
353
|
const autonomy = {
|
|
333
354
|
...unknown,
|
|
334
355
|
...Object.fromEntries(
|
|
@@ -366,6 +387,7 @@ export function writeGrants(
|
|
|
366
387
|
grants: { ...DEFAULT_GRANTS, ...grants },
|
|
367
388
|
configured: GRANT_KINDS.filter((kind) => grants[kind] !== undefined),
|
|
368
389
|
defaultEndpoint: endpointOf(entry),
|
|
390
|
+
verification: verificationOf(entry),
|
|
369
391
|
};
|
|
370
392
|
}
|
|
371
393
|
|
|
@@ -378,6 +400,7 @@ export function listGrants(dir: string = grantsDir()): {
|
|
|
378
400
|
grants: Grants;
|
|
379
401
|
configured: GrantKind[];
|
|
380
402
|
defaultEndpoint: DefaultEndpoint;
|
|
403
|
+
verification: VerificationMode;
|
|
381
404
|
}[];
|
|
382
405
|
error?: string;
|
|
383
406
|
} {
|
|
@@ -394,6 +417,7 @@ export function listGrants(dir: string = grantsDir()): {
|
|
|
394
417
|
grants: { ...DEFAULT_GRANTS, ...grants },
|
|
395
418
|
configured: GRANT_KINDS.filter((kind) => grants[kind] !== undefined),
|
|
396
419
|
defaultEndpoint: endpointOf(entry),
|
|
420
|
+
verification: verificationOf(entry),
|
|
397
421
|
};
|
|
398
422
|
}),
|
|
399
423
|
};
|
package/src/core/branch.ts
CHANGED
|
@@ -5,6 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import { matchCommitFlavor } from "./commit-flavors";
|
|
6
6
|
import { gitContext } from "./git";
|
|
7
7
|
import {
|
|
8
|
+
branchGlobPattern,
|
|
8
9
|
readConfig,
|
|
9
10
|
resolveBranchPolicy as resolveConfiguredBranchPolicy,
|
|
10
11
|
resolveCommitPolicy as resolveConfiguredCommitPolicy,
|
|
@@ -15,6 +16,47 @@ import {
|
|
|
15
16
|
type WorkspaceCommitPolicy,
|
|
16
17
|
} from "./workspaces";
|
|
17
18
|
import { vcsCliIdentity, vcsConfig } from "./vcs-config";
|
|
19
|
+
import {
|
|
20
|
+
ReleaseTrackError,
|
|
21
|
+
namingGlob,
|
|
22
|
+
trackBranches,
|
|
23
|
+
workspaceReleaseTracks,
|
|
24
|
+
} from "./release-tracks";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Release tracks extend the branch policy: every track's long-lived branches
|
|
28
|
+
* are protected (the union across tracks plus the explicit protected list),
|
|
29
|
+
* and the tracks' naming templates are allowed branch patterns. A track read
|
|
30
|
+
* that must fail closed (a `critical` field) throws, so callers block.
|
|
31
|
+
*/
|
|
32
|
+
const withReleaseTracks = (
|
|
33
|
+
workspaceRoot: string,
|
|
34
|
+
policy: ReturnType<typeof resolveConfiguredBranchPolicy>,
|
|
35
|
+
): ReturnType<typeof resolveConfiguredBranchPolicy> => {
|
|
36
|
+
const read = workspaceReleaseTracks(workspaceRoot);
|
|
37
|
+
if (read.error) throw new ReleaseTrackError(read.error);
|
|
38
|
+
if (read.tracks.length === 0 && read.protectedBranches.length === 0) return policy;
|
|
39
|
+
const known = new Set(policy.allowed.map((pattern) => pattern.source));
|
|
40
|
+
const allowed = [...policy.allowed];
|
|
41
|
+
for (const glob of read.tracks.flatMap((track) => Object.values(track.naming).map(namingGlob))) {
|
|
42
|
+
const pattern = branchGlobPattern(glob);
|
|
43
|
+
if (!known.has(pattern.source)) {
|
|
44
|
+
known.add(pattern.source);
|
|
45
|
+
allowed.push(pattern);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
...policy,
|
|
50
|
+
allowed,
|
|
51
|
+
protected: new Set([
|
|
52
|
+
...policy.protected,
|
|
53
|
+
// Fail-safe: every branch any track entry names, including dropped ones.
|
|
54
|
+
...[...trackBranches(read.tracks), ...read.protectedBranches].map((name) =>
|
|
55
|
+
name.toLowerCase(),
|
|
56
|
+
),
|
|
57
|
+
]),
|
|
58
|
+
};
|
|
59
|
+
};
|
|
18
60
|
|
|
19
61
|
function effectiveWorkspacePolicy(
|
|
20
62
|
workspaceRoot: string,
|
|
@@ -53,9 +95,12 @@ function effectiveWorkspacePolicy(
|
|
|
53
95
|
const config = readConfig();
|
|
54
96
|
return kind === "branch"
|
|
55
97
|
? {
|
|
56
|
-
branchPolicy:
|
|
57
|
-
|
|
58
|
-
|
|
98
|
+
branchPolicy: withReleaseTracks(
|
|
99
|
+
workspaceRoot,
|
|
100
|
+
resolveConfiguredBranchPolicy(
|
|
101
|
+
config,
|
|
102
|
+
selected.policy ? { branchPolicy: selected.policy as WorkspaceBranchPolicy } : null,
|
|
103
|
+
),
|
|
59
104
|
),
|
|
60
105
|
provenance: { branchPolicy: selected.source },
|
|
61
106
|
}
|
|
@@ -262,10 +307,16 @@ export const verifyPushIdentity = (
|
|
|
262
307
|
return { ok: true };
|
|
263
308
|
};
|
|
264
309
|
// RL-01: malformed vcs.json blocks branch resolution with an exact-path error.
|
|
265
|
-
|
|
310
|
+
// With release tracks, the base is the resolved track's base branch.
|
|
311
|
+
const baseBranch = (cwd: string): { base: string; trackBranches: string[] } | { error: string } => {
|
|
266
312
|
const resolved = vcsConfig("resolve", cwd);
|
|
267
313
|
if (resolved.ok === false) return { error: String(resolved.error) };
|
|
268
|
-
|
|
314
|
+
// Creating a branch on an undetermined release line would guess the base.
|
|
315
|
+
if (resolved.releaseTrack?.blocking) return { error: String(resolved.releaseTrack.blocking) };
|
|
316
|
+
return {
|
|
317
|
+
base: String(resolved.baseBranch ?? resolved.defaultTargetBranch ?? "develop"),
|
|
318
|
+
trackBranches: resolved.releaseTrack ? trackBranches(resolved.releaseTrack.tracks) : [],
|
|
319
|
+
};
|
|
269
320
|
};
|
|
270
321
|
const DECLARE_RE = /^\s*\*+Branch:\*+\s*`?([^`\s|]+)`?\s*$/gim;
|
|
271
322
|
const USE_CURRENT_RE = /^\s*\*+Branch:\*+\s*use-current\s*$/im;
|
|
@@ -407,7 +458,13 @@ export const docsBranch = ({
|
|
|
407
458
|
if ("error" in baseResolved) return { error: baseResolved.error };
|
|
408
459
|
const base = baseResolved.base;
|
|
409
460
|
|
|
410
|
-
if (
|
|
461
|
+
if (
|
|
462
|
+
current === base ||
|
|
463
|
+
current === "main" ||
|
|
464
|
+
current === "master" ||
|
|
465
|
+
current === "develop" ||
|
|
466
|
+
baseResolved.trackBranches.includes(current)
|
|
467
|
+
) {
|
|
411
468
|
let slug = "";
|
|
412
469
|
if (plan_path) {
|
|
413
470
|
const plan = path.isAbsolute(plan_path) ? plan_path : path.join(cwd, plan_path);
|
package/src/core/config.ts
CHANGED
|
@@ -287,6 +287,14 @@ export const describeConfigSource = (
|
|
|
287
287
|
}
|
|
288
288
|
};
|
|
289
289
|
|
|
290
|
+
/** The global vcs.json path (WORKFLOW_VCS_CONFIG overrides it). */
|
|
291
|
+
export const vcsConfigPath = (): string =>
|
|
292
|
+
process.env.WORKFLOW_VCS_CONFIG ?? path.join(configDir(), "vcs.json");
|
|
293
|
+
|
|
294
|
+
/** An allowed-branch glob (`feature/*`) as a case-insensitive whole-name RegExp. */
|
|
295
|
+
export const branchGlobPattern = (glob: string): RegExp =>
|
|
296
|
+
new RegExp(`^${glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i");
|
|
297
|
+
|
|
290
298
|
export const resolveBranchPolicy = (
|
|
291
299
|
config: ToolkitConfig,
|
|
292
300
|
workspace?: { branchPolicy?: Record<string, any> } | null,
|
|
@@ -315,9 +323,7 @@ export const resolveBranchPolicy = (
|
|
|
315
323
|
},
|
|
316
324
|
config,
|
|
317
325
|
);
|
|
318
|
-
const allowed = merged.allowed.map(
|
|
319
|
-
(p) => new RegExp(`^${p.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i"),
|
|
320
|
-
);
|
|
326
|
+
const allowed = merged.allowed.map(branchGlobPattern);
|
|
321
327
|
return {
|
|
322
328
|
preset,
|
|
323
329
|
allowed,
|
package/src/core/methods.ts
CHANGED
|
@@ -114,10 +114,10 @@ Autonomy contract:
|
|
|
114
114
|
inferred, or guess. Prose is a note: only a configured check the CLI ran
|
|
115
115
|
(\`workit check <name>\`) satisfies a gate; an ad-hoc
|
|
116
116
|
\`workit check -- <cmd>\` never does.
|
|
117
|
-
- Author is not verifier:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
- Author is not verifier: your own verdict is \`--self\` (after an observed
|
|
118
|
+
\`workit check test\`), shown as self-reviewed, never verified. High risk,
|
|
119
|
+
\`verification: independent\` and merge need a verifier with its own
|
|
120
|
+
session (\`WORKIT_SESSION_ID=<lead>-v<n>\`; Claude Code subagents get one).
|
|
121
121
|
- A local-commit endpoint does not imply PR readiness, and a local commit alone
|
|
122
122
|
is not evidence of a requested remote push. Before reporting delivery,
|
|
123
123
|
reconcile every requested item and observe it (\`workit verify-delivery\`).
|
|
@@ -130,7 +130,9 @@ PATH, \`npx -y @brainervirus/workit-cli\`). \`busy\` is retryable
|
|
|
130
130
|
The task families (task, policy, evidence, finding, decision, worker, state)
|
|
131
131
|
are optional continuity for tracked work; each branch has one implicit task
|
|
132
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.
|
|
133
|
+
records a user choice; it never authorizes an effect. Judge work once with
|
|
134
|
+
\`workit policy assess --judge risk= behavior= product-choice= plan=\`; an
|
|
135
|
+
open choice or missing plan blocks edits until recorded.
|
|
134
136
|
Omit revisions; a revision_conflict means one you passed is stale, so re-read.
|
|
135
137
|
Across repositories, bind each item to its checkout, branch and endpoint,
|
|
136
138
|
resolve competing targets before a mutation, and reconcile an uncertain
|