@workit-dev/core 0.0.1 → 1.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/LICENSE +21 -0
- package/README.md +22 -43
- package/package.json +56 -6
- package/schemas/config.json +74 -0
- package/schemas/vcs.json +79 -0
- package/schemas/workspaces.json +1029 -0
- package/schemas/youtrack.json +84 -0
- package/scripts/analyze-release-scope.ts +215 -0
- package/scripts/bundled-deps.json +131 -0
- package/scripts/fixtures/sample-plan.md +15 -0
- package/scripts/fixtures/sample-sdd/progress.md +2 -0
- package/scripts/install-codex-plugin.sh +28 -0
- package/scripts/install-cursor-plugin.sh +146 -0
- package/scripts/install-opencode-plugin.sh +79 -0
- package/scripts/install-pi-package.sh +56 -0
- package/scripts/lib/config-dir.sh +28 -0
- package/scripts/publish-changed-packages.ts +395 -0
- package/scripts/rewrite-workspace-deps.ts +53 -0
- package/scripts/run-cursor-mcp.sh +6 -0
- package/scripts/sync-release-manifests.ts +82 -0
- package/scripts/sync-runtime.sh +142 -0
- package/scripts/validate-cursor-marketplace.ts +171 -0
- package/scripts/verify-bundle-sources.ts +134 -0
- package/scripts/write-schemas.ts +16 -0
- package/skills/workit-architecture/SKILL.md +72 -0
- package/skills/workit-architecture/agents/openai.yaml +3 -0
- package/skills/workit-architecture/references/instruction-files.md +52 -0
- package/skills/workit-architecture/references/vocabulary.md +53 -0
- package/skills/workit-bdd/SKILL.md +53 -0
- package/skills/workit-continue/SKILL.md +54 -0
- package/skills/workit-debug/SKILL.md +45 -0
- package/skills/workit-deslop/SKILL.md +58 -0
- package/skills/workit-fanout/SKILL.md +71 -0
- package/skills/workit-fanout/references/brief.md +141 -0
- package/skills/workit-fanout/references/orchestration.md +169 -0
- package/skills/workit-implement/SKILL.md +71 -0
- package/skills/workit-retro/SKILL.md +72 -0
- package/skills/workit-retro/agents/openai.yaml +3 -0
- package/skills/workit-retro/references/sources.md +35 -0
- package/skills/workit-retro/references/steering.md +39 -0
- package/skills/workit-review/SKILL.md +66 -0
- 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 +61 -0
- package/skills/workit-shape/references/mockups.md +15 -0
- package/skills/workit-shape/references/slicing.md +44 -0
- package/skills/workit-ship/SKILL.md +71 -0
- package/skills/workit-ship/references/changelog.md +44 -0
- package/skills/workit-ship/references/pr-body.md +32 -0
- package/skills/workit-ship/references/release.md +39 -0
- package/skills/workit-test-audit/SKILL.md +54 -0
- package/skills/workit-verify-app/SKILL.md +68 -0
- package/skills/workit-verify-app/references/template.md +51 -0
- package/src/autonomy.ts +910 -0
- package/src/check-config.ts +613 -0
- package/src/checks.ts +534 -0
- package/src/ci-waiter.ts +159 -0
- package/src/core/boundary.ts +55 -0
- package/src/core/branch-policy.ts +112 -0
- package/src/core/branch.ts +1192 -0
- package/src/core/changelog-gate.ts +349 -0
- package/src/core/changelog-merge.ts +214 -0
- package/src/core/changelog.ts +392 -0
- package/src/core/cleanup.ts +638 -0
- package/src/core/cli-locate.ts +173 -0
- package/src/core/commit-flavors.ts +65 -0
- package/src/core/config-schemas.ts +653 -0
- package/src/core/config.ts +351 -0
- package/src/core/context-read.ts +144 -0
- package/src/core/docs-gate.ts +69 -0
- package/src/core/finding-evidence.ts +89 -0
- package/src/core/finding-queue.ts +36 -0
- package/src/core/git.ts +73 -0
- package/src/core/gitignore.ts +52 -0
- package/src/core/heavy-eta.ts +170 -0
- package/src/core/heavy-lease.ts +290 -0
- package/src/core/heavy-rules.ts +373 -0
- package/src/core/heavy-scope.ts +131 -0
- package/src/core/heavy-words.ts +83 -0
- package/src/core/heavy.ts +535 -0
- package/src/core/hygiene.ts +149 -0
- package/src/core/init.ts +218 -0
- package/src/core/live-session.ts +333 -0
- package/src/core/logger.ts +310 -0
- package/src/core/methods.ts +185 -0
- package/src/core/model-fallback.ts +208 -0
- package/src/core/node-resolve.mjs +249 -0
- package/src/core/operation-input.ts +632 -0
- package/src/core/package-root.ts +28 -0
- package/src/core/policy/attributes.ts +97 -0
- package/src/core/policy/bookkeeping.ts +119 -0
- package/src/core/policy/carry.ts +122 -0
- package/src/core/policy/check-evidence.ts +115 -0
- package/src/core/policy/current-judgment.ts +20 -0
- package/src/core/policy/derive.ts +387 -0
- package/src/core/policy/judgment.ts +246 -0
- package/src/core/policy/lane.ts +562 -0
- package/src/core/policy/pr-lane.ts +32 -0
- package/src/core/policy/substance.ts +365 -0
- package/src/core/policy-resolver.ts +94 -0
- package/src/core/pr-create.ts +448 -0
- package/src/core/pr-template.ts +162 -0
- package/src/core/release-notes.ts +197 -0
- package/src/core/release-tracks.ts +779 -0
- package/src/core/remotes.ts +436 -0
- package/src/core/repo-context.ts +542 -0
- package/src/core/repo-paths.ts +29 -0
- package/src/core/resources.ts +1299 -0
- package/src/core/role-models.ts +603 -0
- package/src/core/route-intent.ts +305 -0
- package/src/core/rules.ts +65 -0
- package/src/core/runtime-identity.ts +63 -0
- package/src/core/safe-write.ts +87 -0
- package/src/core/scope-glob.ts +56 -0
- package/src/core/scripts.ts +6 -0
- package/src/core/skill-manifests.ts +173 -0
- package/src/core/store-lock.ts +374 -0
- package/src/core/support-matrix.ts +24 -0
- package/src/core/task-context.ts +247 -0
- package/src/core/task-contract.ts +1279 -0
- package/src/core/task-delivery.ts +109 -0
- package/src/core/task-engine.ts +2248 -0
- package/src/core/task-evaluation.ts +1147 -0
- package/src/core/task-store.ts +2649 -0
- package/src/core/task-view.ts +180 -0
- package/src/core/templates.ts +22 -0
- package/src/core/tracker-issues.ts +174 -0
- package/src/core/vcs-config.ts +540 -0
- package/src/core/workers.ts +62 -0
- package/src/core/workit-paths.mjs +242 -0
- package/src/core/workspace-branch-policy.ts +123 -0
- package/src/core/workspaces.ts +1745 -0
- package/src/core/youtrack-backend.ts +288 -0
- package/src/core/youtrack-tools.ts +260 -0
- package/src/core/youtrack-writes.ts +73 -0
- package/src/core/youtrack-ytrack.ts +217 -0
- package/src/core/youtrack.ts +454 -0
- package/src/core/ytrack-skill.ts +297 -0
- package/src/core.ts +171 -0
- package/src/deslop/lex.ts +271 -0
- package/src/deslop/scan.ts +232 -0
- package/src/duration.ts +27 -0
- package/src/fanout-brief.ts +486 -0
- package/src/fanout-check.ts +455 -0
- package/src/fanout-landed.ts +370 -0
- package/src/fanout-stack.ts +108 -0
- package/src/fanout-status.ts +362 -0
- package/src/fanout-worktree.ts +893 -0
- package/src/fanout.ts +1473 -0
- package/src/forge/exec.ts +385 -0
- package/src/forge/github.ts +831 -0
- package/src/forge/gitlab.ts +906 -0
- package/src/forge/issue-forge.ts +209 -0
- package/src/forge/issue-link.ts +468 -0
- package/src/forge/issue-ops.ts +314 -0
- package/src/forge/merge-gate-base-red.ts +72 -0
- package/src/forge/pr-body.ts +100 -0
- package/src/forge/pr-edit.ts +292 -0
- package/src/forge/pr-ops.ts +1109 -0
- package/src/forge/pr-status.ts +10 -0
- package/src/forge/redact.ts +195 -0
- package/src/forge/report.ts +994 -0
- package/src/forge/reruns.ts +132 -0
- package/src/forge/resolve.ts +509 -0
- package/src/forge/types.ts +242 -0
- package/src/forge/verify.ts +398 -0
- package/src/git/default-base.ts +130 -0
- package/src/git/env-redirect.ts +28 -0
- package/src/git/facts.ts +122 -0
- package/src/git/ops.ts +1823 -0
- package/src/git/refs-read.ts +332 -0
- package/src/git/release.ts +1020 -0
- package/src/git/rev.ts +1175 -0
- package/src/git/session-hook.ts +597 -0
- package/src/git/stage.ts +63 -0
- package/src/hooks/agent-model.ts +149 -0
- package/src/hooks/budget-signal.ts +284 -0
- package/src/hooks/context.ts +461 -0
- package/src/hooks/cwd-normalize.ts +79 -0
- package/src/hooks/deadline.ts +228 -0
- package/src/hooks/delivery-verbs.ts +182 -0
- package/src/hooks/descriptor.ts +124 -0
- package/src/hooks/handle.ts +433 -0
- package/src/hooks/heavy-gate.ts +99 -0
- package/src/hooks/hook-runs.ts +130 -0
- package/src/hooks/hosts/claude-code.ts +380 -0
- package/src/hooks/hosts/codex.ts +372 -0
- package/src/hooks/hosts/cursor.ts +419 -0
- package/src/hooks/hosts/fields.ts +98 -0
- package/src/hooks/hosts/opencode.ts +79 -0
- package/src/hooks/hosts/pi.ts +78 -0
- package/src/hooks/index.ts +60 -0
- package/src/hooks/model-limit.ts +378 -0
- package/src/hooks/model-switch.ts +35 -0
- package/src/hooks/policy.ts +29 -0
- package/src/hooks/process.ts +48 -0
- package/src/hooks/protocol.ts +135 -0
- package/src/hooks/raw-git.ts +2289 -0
- package/src/hooks/repeat-failure.ts +115 -0
- package/src/hooks/run.ts +54 -0
- package/src/hooks/session-marker.ts +151 -0
- package/src/hooks/shell-words.ts +532 -0
- package/src/hooks/skill-nudge.ts +295 -0
- package/src/hooks/spawn-gate.ts +72 -0
- package/src/hooks/standing-signal.ts +99 -0
- package/src/hooks/standing.ts +225 -0
- package/src/hooks/steering.ts +74 -0
- package/src/hooks/stop.ts +408 -0
- package/src/hooks/worktree-confine.ts +415 -0
- package/src/hooks/write-gate.ts +1174 -0
- package/src/host-session.ts +459 -0
- package/src/knowledge.ts +368 -0
- package/src/ledger.ts +2913 -0
- package/src/stack.ts +2570 -0
- package/src/store/event-log.ts +217 -0
- package/src/store/notes.ts +29 -0
- package/src/store/patch.ts +135 -0
- package/src/store/paths.ts +368 -0
- package/src/store/reduce.ts +203 -0
- package/src/test-audit-mocks.ts +309 -0
- package/src/verdict-cover.ts +151 -0
- package/src/worktree-deps.ts +142 -0
- package/templates/headers.md +3 -0
- package/templates/hygiene/.editorconfig +8 -0
- package/templates/hygiene/.gitattributes +3 -0
- package/templates/hygiene/CHANGELOG.md +14 -0
- package/templates/hygiene/CONTRIBUTING.md +3 -0
- package/templates/hygiene/LICENSE +21 -0
- package/templates/hygiene/README.md +3 -0
- package/templates/issue-update.md +5 -0
- package/templates/plan-template.md +26 -0
- package/templates/spec-template.md +64 -0
- package/templates/workit-contract.md +13 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
// Standing-order deltas for running workers (#320, decision mvi7psa4). A
|
|
2
|
+
// worker's brief carries the orders in force when it was rendered; an order
|
|
3
|
+
// the lead adds or clears later reaches the worker on its next hooked tool
|
|
4
|
+
// call, once per revision (standing-signal.ts). A worker of a fanout is a
|
|
5
|
+
// subagent or child of one of its leads, a session id handed out under one
|
|
6
|
+
// (`<lead>:<x>`, `<lead>-w-<slice>`), or any session on one of its slice
|
|
7
|
+
// branches; the lead itself is never one, nor is a subagent of the lead whose
|
|
8
|
+
// type is not a Workit worker or judge (W2.27).
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { currentBranch } from "../git/rev";
|
|
12
|
+
import { activeStanding, readLedger, type ReadRow } from "../ledger";
|
|
13
|
+
import {
|
|
14
|
+
gitStoreDir,
|
|
15
|
+
readStandingRev,
|
|
16
|
+
readStandingSeen,
|
|
17
|
+
standingSeenFile,
|
|
18
|
+
writeStandingSeen,
|
|
19
|
+
} from "./standing-signal";
|
|
20
|
+
import type { HostDescriptor } from "./descriptor";
|
|
21
|
+
import { CLAUDE_CODE_DESCRIPTOR } from "./hosts/claude-code";
|
|
22
|
+
import { codexDescriptor } from "./hosts/codex";
|
|
23
|
+
import { CURSOR_DESCRIPTOR } from "./hosts/cursor";
|
|
24
|
+
import { OPENCODE_DESCRIPTOR } from "./hosts/opencode";
|
|
25
|
+
import { PI_DESCRIPTOR } from "./hosts/pi";
|
|
26
|
+
import type { HookInput, HostId } from "./protocol";
|
|
27
|
+
import { workitRole } from "./steering";
|
|
28
|
+
|
|
29
|
+
type Plan = { name: string; leads: string[]; branches: string[] };
|
|
30
|
+
|
|
31
|
+
const readPlans = (store: string): Plan[] => {
|
|
32
|
+
const dir = path.join(store, "fanouts");
|
|
33
|
+
let names: string[];
|
|
34
|
+
try {
|
|
35
|
+
names = fs.readdirSync(dir).filter((name) => name.endsWith(".json"));
|
|
36
|
+
} catch {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
const plans: Plan[] = [];
|
|
40
|
+
for (const name of names) {
|
|
41
|
+
try {
|
|
42
|
+
const raw = JSON.parse(fs.readFileSync(path.join(dir, name), "utf8")) as Record<
|
|
43
|
+
string,
|
|
44
|
+
unknown
|
|
45
|
+
>;
|
|
46
|
+
if (typeof raw.name !== "string" || !Array.isArray(raw.slices)) continue;
|
|
47
|
+
const texts = (value: unknown): string[] =>
|
|
48
|
+
Array.isArray(value)
|
|
49
|
+
? value.filter((item): item is string => typeof item === "string" && item !== "")
|
|
50
|
+
: [];
|
|
51
|
+
plans.push({
|
|
52
|
+
name: raw.name,
|
|
53
|
+
leads: texts([raw.leadSession, ...texts(raw.formerLeads)]),
|
|
54
|
+
branches: texts(
|
|
55
|
+
raw.slices.map((slice) =>
|
|
56
|
+
slice && typeof slice === "object" ? (slice as { branch?: unknown }).branch : null,
|
|
57
|
+
),
|
|
58
|
+
),
|
|
59
|
+
});
|
|
60
|
+
} catch {
|
|
61
|
+
// An unreadable plan has no workers to tell.
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return plans;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/** The lead a subagent or child reports to, when the host names it. */
|
|
68
|
+
const leadOf = (input: HookInput): string | null =>
|
|
69
|
+
input.session.parentId ??
|
|
70
|
+
(input.session.agentId ? input.session.id : null) ??
|
|
71
|
+
(input.session.id.includes(":") ? input.session.id.split(":")[0] : null);
|
|
72
|
+
|
|
73
|
+
const DESCRIPTORS: Readonly<Record<HostId, HostDescriptor>> = {
|
|
74
|
+
claude_code: CLAUDE_CODE_DESCRIPTOR,
|
|
75
|
+
opencode: OPENCODE_DESCRIPTOR,
|
|
76
|
+
codex_cli: codexDescriptor("codex_cli"),
|
|
77
|
+
codex_desktop: codexDescriptor("codex_desktop"),
|
|
78
|
+
cursor: CURSOR_DESCRIPTOR,
|
|
79
|
+
pi: PI_DESCRIPTOR,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* A subagent of the lead whose host names its type works for the lead's
|
|
84
|
+
* fanouts only as a Workit worker or judge (W2.27), spelled its host's way
|
|
85
|
+
* (workitRole: `workit:implementer` on Claude Code, `workit-implementer`
|
|
86
|
+
* elsewhere), so the orders and the role steering agree: an inventory or Explore
|
|
87
|
+
* agent of the same lead gets no orders unless it sits on a slice branch.
|
|
88
|
+
* A host that names no type (OpenCode children) keeps the lead link alone.
|
|
89
|
+
*/
|
|
90
|
+
const workerOfLead = (input: HookInput): boolean =>
|
|
91
|
+
input.session.agentType === null ||
|
|
92
|
+
workitRole(DESCRIPTORS[input.host], input.session.agentType) !== null;
|
|
93
|
+
|
|
94
|
+
const worksFor = (plan: Plan, input: HookInput, branch: () => string | null): boolean => {
|
|
95
|
+
const session = input.session.id;
|
|
96
|
+
if (plan.leads.includes(session) && !input.session.agentId) return false;
|
|
97
|
+
const lead = leadOf(input);
|
|
98
|
+
if (lead && plan.leads.includes(lead) && workerOfLead(input)) return true;
|
|
99
|
+
if (plan.leads.some((each) => session.startsWith(`${each}-w-`))) return true;
|
|
100
|
+
const current = branch();
|
|
101
|
+
return current !== null && plan.branches.includes(current);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
type Standing = { rev: string; orders: Map<string, ReadRow>; rows: readonly ReadRow[] };
|
|
105
|
+
|
|
106
|
+
/** Orders in force now for every fanout this agent works for, keyed by row id. */
|
|
107
|
+
const standingFor = (input: HookInput, store: string, rev: string): Standing => {
|
|
108
|
+
const orders = new Map<string, ReadRow>();
|
|
109
|
+
const ledger = readLedger(input.cwd);
|
|
110
|
+
const rows = ledger.ok ? ledger.value.rows : [];
|
|
111
|
+
let branch: string | null | undefined;
|
|
112
|
+
const lazyBranch = () => (branch === undefined ? (branch = currentBranch(input.cwd)) : branch);
|
|
113
|
+
for (const plan of readPlans(store))
|
|
114
|
+
if (worksFor(plan, input, lazyBranch))
|
|
115
|
+
for (const row of activeStanding(rows, plan.name, plan.leads.length ? plan.leads : null))
|
|
116
|
+
orders.set(row.id, row);
|
|
117
|
+
return { rev, orders, rows };
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/** The same store path the launchers' fast paths compute, so they read the same seen file. */
|
|
121
|
+
const located = (input: HookInput) => {
|
|
122
|
+
const store = input.session.id ? gitStoreDir(input.cwd) : null;
|
|
123
|
+
const rev = store ? readStandingRev(store) : null;
|
|
124
|
+
if (!store || rev === null) return null;
|
|
125
|
+
return { store, rev, file: standingSeenFile(store, input.session.id, input.session.agentId) };
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* A new subagent's brief already carries the orders in force: its baseline,
|
|
130
|
+
* so only later changes reach it.
|
|
131
|
+
*/
|
|
132
|
+
export const seedStanding = (input: HookInput): void => {
|
|
133
|
+
try {
|
|
134
|
+
const where = located(input);
|
|
135
|
+
if (!where) return;
|
|
136
|
+
const now = standingFor(input, where.store, where.rev);
|
|
137
|
+
writeStandingSeen(where.file, { rev: where.rev, ids: [...now.orders.keys()] });
|
|
138
|
+
} catch {
|
|
139
|
+
// Without a baseline the first delta recaps every order in force.
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const fanoutOf = (row: ReadRow): string => (typeof row.fanout === "string" ? row.fanout : "?");
|
|
144
|
+
|
|
145
|
+
/** The orders added and dropped since `ids`, as one model-visible block, or null. */
|
|
146
|
+
const changedSince = (now: Standing, ids: readonly string[]): string | null => {
|
|
147
|
+
const known = new Set(ids);
|
|
148
|
+
const added = [...now.orders.values()].filter((row) => !known.has(row.id));
|
|
149
|
+
const byId = new Map(now.rows.map((row) => [row.id, row]));
|
|
150
|
+
const dropped = ids
|
|
151
|
+
.filter((id) => !now.orders.has(id))
|
|
152
|
+
.map((id) => byId.get(id))
|
|
153
|
+
.filter((row): row is ReadRow => row !== undefined);
|
|
154
|
+
if (!added.length && !dropped.length) return null;
|
|
155
|
+
const fanouts = [...new Set([...added, ...dropped].map(fanoutOf))].join(", ");
|
|
156
|
+
const lines = [
|
|
157
|
+
`Workit: the lead changed the standing orders for fanout ${fanouts} while you run. They apply to you now and override your brief's STANDING list.`,
|
|
158
|
+
...added.map((row) => ` - new: ${String(row.what)}`),
|
|
159
|
+
...dropped.map(
|
|
160
|
+
(row) =>
|
|
161
|
+
` - no longer in force: ${String(row.what)}${row.stop ? " (the stop is lifted: continue your brief)" : ""}`,
|
|
162
|
+
),
|
|
163
|
+
];
|
|
164
|
+
if (added.some((row) => row.stop === true))
|
|
165
|
+
lines.push(
|
|
166
|
+
"Stop now: make no further changes, close what you opened, and report to the lead with your branch, head SHA, what is done and what is not.",
|
|
167
|
+
);
|
|
168
|
+
return lines.join("\n");
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* What changed since this agent last saw the standing orders; null when
|
|
173
|
+
* nothing did or it works for no fanout. An agent seen for the first time is
|
|
174
|
+
* told every order in force. Marks the revision seen, so a host that keeps
|
|
175
|
+
* each answer in the transcript gets the block once.
|
|
176
|
+
*/
|
|
177
|
+
export const standingDelta = (input: HookInput): string | null => {
|
|
178
|
+
try {
|
|
179
|
+
const where = located(input);
|
|
180
|
+
if (!where) return null;
|
|
181
|
+
const seen = readStandingSeen(where.file);
|
|
182
|
+
if (seen.rev === where.rev) return null;
|
|
183
|
+
const now = standingFor(input, where.store, where.rev);
|
|
184
|
+
writeStandingSeen(where.file, { rev: where.rev, ids: [...now.orders.keys()] });
|
|
185
|
+
return changedSince(now, seen.ids);
|
|
186
|
+
} catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const sinceStart = new Map<string, { rev: string; text: string | null }>();
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* For a host that rebuilds its context on every model call (OpenCode): the
|
|
195
|
+
* changes since this agent's first call, on every call while they stand.
|
|
196
|
+
* The first call records the baseline (its brief carries those orders); the
|
|
197
|
+
* ledger is read again only when the revision moves.
|
|
198
|
+
*/
|
|
199
|
+
export const standingSinceStart = (input: HookInput): string | null => {
|
|
200
|
+
try {
|
|
201
|
+
const store = input.session.id ? gitStoreDir(input.cwd) : null;
|
|
202
|
+
if (!store) return null;
|
|
203
|
+
const rev = readStandingRev(store) ?? "";
|
|
204
|
+
const file = standingSeenFile(store, input.session.id, input.session.agentId);
|
|
205
|
+
const cached = sinceStart.get(file);
|
|
206
|
+
if (cached?.rev === rev) return cached.text;
|
|
207
|
+
let text: string | null = null;
|
|
208
|
+
if (rev) {
|
|
209
|
+
const now = standingFor(input, store, rev);
|
|
210
|
+
const seen = readStandingSeen(file);
|
|
211
|
+
let ids = seen.ids;
|
|
212
|
+
if (seen.rev === null) {
|
|
213
|
+
// Seen before any order existed: every order is new to it.
|
|
214
|
+
ids = cached ? [] : [...now.orders.keys()];
|
|
215
|
+
writeStandingSeen(file, { rev, ids });
|
|
216
|
+
}
|
|
217
|
+
text = changedSince(now, ids);
|
|
218
|
+
}
|
|
219
|
+
sinceStart.set(file, { rev, text });
|
|
220
|
+
if (sinceStart.size > 256) sinceStart.delete(sinceStart.keys().next().value!);
|
|
221
|
+
return text;
|
|
222
|
+
} catch {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// What Workit tells an agent about git, by role: one source for the
|
|
2
|
+
// SubagentStart text (handle.ts) and the raw-git nudge set (raw-git.ts), so
|
|
3
|
+
// the two cannot disagree (#428).
|
|
4
|
+
import { gateCheckNames, loadCheckConfig } from "../check-config";
|
|
5
|
+
import { neededChecks } from "../core/policy/check-evidence";
|
|
6
|
+
import type { HostDescriptor } from "./descriptor";
|
|
7
|
+
import { hostIsolated } from "./worktree-confine";
|
|
8
|
+
|
|
9
|
+
export type WorkitRole = "implementer" | "judge";
|
|
10
|
+
|
|
11
|
+
const ROLES: Readonly<Record<string, WorkitRole>> = {
|
|
12
|
+
implementer: "implementer",
|
|
13
|
+
verifier: "judge",
|
|
14
|
+
reviewer: "judge",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The Workit agent role an agent type names, spelled the host's way
|
|
19
|
+
* (`descriptor.subagents.agentPrefix`): Claude Code namespaces plugin agents
|
|
20
|
+
* (`workit:implementer`, so a bare `workit-implementer` is someone else's);
|
|
21
|
+
* other hosts name custom agents freely (`workit-implementer`).
|
|
22
|
+
*/
|
|
23
|
+
export const workitRole = (descriptor: HostDescriptor, agentType: string): WorkitRole | null => {
|
|
24
|
+
const prefix = descriptor.subagents.agentPrefix;
|
|
25
|
+
const name = agentType.startsWith(prefix) ? agentType.slice(prefix.length) : "";
|
|
26
|
+
return Object.hasOwn(ROLES, name) ? ROLES[name] : null;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** A Claude Code worktree path: `<repo>/.claude/worktrees/<name>`. */
|
|
30
|
+
const CLAUDE_WORKTREE = /[\\/]\.claude[\\/]worktrees[\\/]/;
|
|
31
|
+
|
|
32
|
+
export type RoleSteering = {
|
|
33
|
+
role: WorkitRole | null;
|
|
34
|
+
/** The host put the agent in its own worktree. */
|
|
35
|
+
isolated: boolean;
|
|
36
|
+
/** How the start text tells it to run git: `plain` (host isolation refuses a wrapper), `workit`, or not at all. */
|
|
37
|
+
git: "plain" | "workit" | null;
|
|
38
|
+
/** Raw commit/push/PR-create nudges: only for an agent no start text told how to run git. */
|
|
39
|
+
nudges: boolean;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const roleSteering = (role: WorkitRole | null, isolated: boolean): RoleSteering => ({
|
|
43
|
+
role,
|
|
44
|
+
isolated,
|
|
45
|
+
git: role === "implementer" ? (isolated ? "plain" : "workit") : null,
|
|
46
|
+
// An implementer has its git line already; a judge does not commit.
|
|
47
|
+
nudges: role === null,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/** The steering for one agent, as the host names it (no agent type: the lead). */
|
|
51
|
+
export const agentSteering = (
|
|
52
|
+
descriptor: HostDescriptor,
|
|
53
|
+
cwd: string,
|
|
54
|
+
agent: { type: string | null; id: string | null },
|
|
55
|
+
): RoleSteering => {
|
|
56
|
+
const role = agent.type ? workitRole(descriptor, agent.type) : null;
|
|
57
|
+
// An implementer is host-isolated only when the host put it in its own
|
|
58
|
+
// worktree (`.claude/worktrees/agent-<id>`); by default the lead makes one.
|
|
59
|
+
const isolated =
|
|
60
|
+
descriptor.subagents.worktreeIsolation === "native" &&
|
|
61
|
+
(role === "implementer" ? hostIsolated(cwd, agent.id) : CLAUDE_WORKTREE.test(cwd));
|
|
62
|
+
return roleSteering(role, isolated);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The configured checks a judge runs before a verified verdict, as exact
|
|
67
|
+
* commands: `ledger verdict` refuses one while any is red, or missing with
|
|
68
|
+
* CI on the head not green (#509). Null when the repository configures none.
|
|
69
|
+
*/
|
|
70
|
+
export const judgeChecksLine = (cwd: string): string | null => {
|
|
71
|
+
const names = neededChecks("full", gateCheckNames(loadCheckConfig(cwd), "verification"));
|
|
72
|
+
if (!names.length) return null;
|
|
73
|
+
return `Before a verified or tests-verified verdict, run these configured checks on the candidate head, by name and one per call (or cite green CI on the head): ${names.map((name) => `\`workit check ${name}\``).join(", ")} (with \`--cwd <checkout>\` when the candidate is checked out elsewhere). The verdict is refused while one has neither a fresh passing run on that head nor green CI there; an ad-hoc \`workit check -- <cmd>\` does not stand in for one.`;
|
|
74
|
+
};
|