@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,372 @@
|
|
|
1
|
+
// Codex CLI and Desktop: command hooks (hooks/hooks.json) mapped onto the protocol.
|
|
2
|
+
import type { HostDescriptor } from "../descriptor";
|
|
3
|
+
import type { HookDecision, HookEvent, HookEventKind, HostAdapter } from "../protocol";
|
|
4
|
+
import {
|
|
5
|
+
commandText,
|
|
6
|
+
existingDirectory,
|
|
7
|
+
isRecord,
|
|
8
|
+
isWriteTool,
|
|
9
|
+
nonEmpty,
|
|
10
|
+
optionalText,
|
|
11
|
+
writeTargets,
|
|
12
|
+
} from "./fields";
|
|
13
|
+
|
|
14
|
+
export type CodexHost = "codex_cli" | "codex_desktop";
|
|
15
|
+
export type CodexHookEvent =
|
|
16
|
+
| "SessionStart"
|
|
17
|
+
| "UserPromptSubmit"
|
|
18
|
+
| "PreToolUse"
|
|
19
|
+
| "PostToolUse"
|
|
20
|
+
| "SubagentStart"
|
|
21
|
+
| "SubagentStop"
|
|
22
|
+
| "Stop";
|
|
23
|
+
// The session-start.command.input schema in codex-cli 0.160.1 lists fork as a source.
|
|
24
|
+
type SessionSource = "startup" | "resume" | "clear" | "compact" | "fork";
|
|
25
|
+
|
|
26
|
+
export type CodexHookInput = {
|
|
27
|
+
hook_event_name: CodexHookEvent;
|
|
28
|
+
session_id: string;
|
|
29
|
+
cwd: string;
|
|
30
|
+
model: string | null;
|
|
31
|
+
permission_mode: string | null;
|
|
32
|
+
transcript_path: string | null;
|
|
33
|
+
source?: SessionSource;
|
|
34
|
+
turn_id?: string;
|
|
35
|
+
/** UserPromptSubmit: the prompt about to be sent. */
|
|
36
|
+
prompt?: string | null;
|
|
37
|
+
tool_name?: string;
|
|
38
|
+
tool_input?: unknown;
|
|
39
|
+
/** The shell command as one string; argv arrays are joined. */
|
|
40
|
+
command?: string;
|
|
41
|
+
tool_use_id?: string;
|
|
42
|
+
/** PostToolUse: the model-facing tool output. */
|
|
43
|
+
tool_response?: string;
|
|
44
|
+
agent_id?: string;
|
|
45
|
+
agent_type?: string;
|
|
46
|
+
agent_transcript_path?: string | null;
|
|
47
|
+
last_assistant_message?: string | null;
|
|
48
|
+
stop_hook_active?: boolean;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type CodexParseResult = { ok: true; data: CodexHookInput } | { ok: false; error: string };
|
|
52
|
+
|
|
53
|
+
export function detectCodexSurface(env: NodeJS.ProcessEnv): CodexHost {
|
|
54
|
+
return env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE === "Codex Desktop" ||
|
|
55
|
+
Boolean(env.CODEX_ELECTRON_RESOURCES_PATH)
|
|
56
|
+
? "codex_desktop"
|
|
57
|
+
: "codex_cli";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const CODEX_DESCRIPTOR: HostDescriptor = {
|
|
61
|
+
host: "codex_cli",
|
|
62
|
+
label: "Codex",
|
|
63
|
+
// The hook JSON schemas embedded in the codex-cli 0.160.1 binary
|
|
64
|
+
// (pre-tool-use, user-prompt-submit, subagent-start .command.input/output)
|
|
65
|
+
// and the hooks page: "PreToolUse can intercept Bash, file edits performed
|
|
66
|
+
// through apply_patch, MCP tool calls…"; apply_patch arrives as tool_name
|
|
67
|
+
// "apply_patch" with the patch in tool_input.command (Edit and Write are
|
|
68
|
+
// matcher aliases only).
|
|
69
|
+
verifiedAgainst:
|
|
70
|
+
"codex-cli 0.160.1 (hook JSON schemas in the binary); Codex Desktop 26.901.20858",
|
|
71
|
+
docs: ["https://developers.openai.com/codex/hooks"],
|
|
72
|
+
transport: "hook-process",
|
|
73
|
+
events: {
|
|
74
|
+
"session.start": { support: "native", native: "SessionStart" },
|
|
75
|
+
"context.turn": { support: "native", native: "UserPromptSubmit" },
|
|
76
|
+
"shell.pre": { support: "native", native: "PreToolUse" },
|
|
77
|
+
"tool.pre": { support: "native", native: "PreToolUse" },
|
|
78
|
+
// PreToolUse covers apply_patch; the files come from the patch body.
|
|
79
|
+
"write.pre": { support: "native", native: "PreToolUse" },
|
|
80
|
+
"shell.post": { support: "native", native: "PostToolUse" },
|
|
81
|
+
"subagent.start": { support: "native", native: "SubagentStart" },
|
|
82
|
+
"subagent.stop": { support: "native", native: "SubagentStop" },
|
|
83
|
+
"prompt.submit": { support: "native", native: "UserPromptSubmit" },
|
|
84
|
+
// Codex has PreCompact, but workit does not register it: events list what
|
|
85
|
+
// workit registers (docs/agents/hosts.md, Host parity).
|
|
86
|
+
"compact.pre": { support: "none", native: null },
|
|
87
|
+
// stop.command.input (codex-cli 0.160.1): stop_hook_active,
|
|
88
|
+
// last_assistant_message, turn_id; output `{"decision":"block","reason"}`.
|
|
89
|
+
stop: { support: "native", native: "Stop" },
|
|
90
|
+
},
|
|
91
|
+
shellPolicy: {
|
|
92
|
+
deny: "native",
|
|
93
|
+
channel: "permissionDecision",
|
|
94
|
+
failClosed: false,
|
|
95
|
+
rewrite: "none",
|
|
96
|
+
},
|
|
97
|
+
context: {
|
|
98
|
+
sessionStart: "native",
|
|
99
|
+
perTurn: "native",
|
|
100
|
+
afterCompact: "native",
|
|
101
|
+
task: "single-active",
|
|
102
|
+
// UserPromptSubmit additionalContext stays in the thread as developer context.
|
|
103
|
+
turnResend: "on-change",
|
|
104
|
+
},
|
|
105
|
+
subagents: {
|
|
106
|
+
identity: "native",
|
|
107
|
+
parentBinding: "none",
|
|
108
|
+
blockStart: "none",
|
|
109
|
+
worktreeIsolation: "undocumented",
|
|
110
|
+
maxConcurrency: "undocumented",
|
|
111
|
+
agentPrefix: "workit-",
|
|
112
|
+
},
|
|
113
|
+
provenance: { sessionId: "native", agentIdOnTool: "partial", postToolObserve: "native" },
|
|
114
|
+
interaction: { questions: "none", writeBoundary: "partial" },
|
|
115
|
+
stopControl: "native",
|
|
116
|
+
shellAvailable: "native",
|
|
117
|
+
perEventCost: "low",
|
|
118
|
+
capabilities: [
|
|
119
|
+
{
|
|
120
|
+
name: "known_product_writes",
|
|
121
|
+
surface: "PreToolUse",
|
|
122
|
+
refs: ["PreToolUse"],
|
|
123
|
+
requires: [],
|
|
124
|
+
assurance: "unavailable",
|
|
125
|
+
reason: "file writes are host-policy; PreToolUse allows write tools",
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: "fresh-context-review",
|
|
129
|
+
surface: "SubagentStart",
|
|
130
|
+
refs: ["SubagentStart"],
|
|
131
|
+
requires: ["event:subagent.start"],
|
|
132
|
+
observed: ["subagent.start"],
|
|
133
|
+
assurance: "agent_guided",
|
|
134
|
+
reason:
|
|
135
|
+
"independent review runs as a bounded subagent; evidence evaluation enforces reviewer exclusivity, and stops remain untrusted",
|
|
136
|
+
unavailableReason: "Codex subagent lifecycle hooks are unavailable for independent review",
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: "native_subagents",
|
|
140
|
+
surface: "SubagentStart/SubagentStop",
|
|
141
|
+
refs: ["SubagentStart", "SubagentStop"],
|
|
142
|
+
requires: ["event:subagent.start", "event:subagent.stop"],
|
|
143
|
+
observed: ["subagent.start", "subagent.stop"],
|
|
144
|
+
assurance: "agent_guided",
|
|
145
|
+
reason: "Codex reports stable child identities, but cannot block creation",
|
|
146
|
+
unavailableReason: "Codex subagent lifecycle hooks are untrusted or incomplete",
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: "native_subagent_start",
|
|
150
|
+
surface: "SubagentStart",
|
|
151
|
+
refs: ["SubagentStart"],
|
|
152
|
+
requires: ["event:subagent.start"],
|
|
153
|
+
observed: ["subagent.start"],
|
|
154
|
+
assurance: "agent_guided",
|
|
155
|
+
reason:
|
|
156
|
+
"SubagentStart supplies identity and bounded read-only guidance; continue:false cannot stop creation",
|
|
157
|
+
unavailableReason: "SubagentStart hook is untrusted or unavailable",
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: "arbitrary_shell_write",
|
|
161
|
+
surface: "unobservable_shell",
|
|
162
|
+
refs: ["PreToolUse"],
|
|
163
|
+
requires: [],
|
|
164
|
+
assurance: "unavailable",
|
|
165
|
+
reason:
|
|
166
|
+
"Only covered known tool inputs are interceptable; specialized and write_stdin paths are not complete",
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: "compact_context",
|
|
170
|
+
surface: "SessionStart",
|
|
171
|
+
refs: ["SessionStart"],
|
|
172
|
+
requires: ["context.afterCompact"],
|
|
173
|
+
observed: ["session.start"],
|
|
174
|
+
assurance: "agent_guided",
|
|
175
|
+
reason: "SessionStart source=compact is the single restore path",
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
export const codexDescriptor = (host: CodexHost): HostDescriptor => ({ ...CODEX_DESCRIPTOR, host });
|
|
181
|
+
|
|
182
|
+
const EVENTS: Record<CodexHookEvent, HookEventKind> = {
|
|
183
|
+
SessionStart: "session.start",
|
|
184
|
+
UserPromptSubmit: "context.turn",
|
|
185
|
+
PreToolUse: "shell.pre",
|
|
186
|
+
PostToolUse: "shell.post",
|
|
187
|
+
SubagentStart: "subagent.start",
|
|
188
|
+
SubagentStop: "subagent.stop",
|
|
189
|
+
Stop: "stop",
|
|
190
|
+
};
|
|
191
|
+
const SHELL_TOOLS = new Set(["bash", "unified-exec"]);
|
|
192
|
+
const isShellTool = (name: unknown) => SHELL_TOOLS.has(String(name).toLowerCase());
|
|
193
|
+
const SOURCES = new Set<string>(["startup", "resume", "clear", "compact", "fork"]);
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Read a Codex payload, checking only what each mapping needs (D17): the
|
|
197
|
+
* event and cwd always, the shell command for shell tools, the tool name for
|
|
198
|
+
* PreToolUse, and the agent id for SubagentStart. Every other field is
|
|
199
|
+
* optional, and unknown keys or values (a new permission mode, say) are
|
|
200
|
+
* ignored, so a Codex release can never turn branch policy off.
|
|
201
|
+
*/
|
|
202
|
+
export const parseCodexHookInput = (value: unknown): CodexParseResult => {
|
|
203
|
+
if (!isRecord(value) || !Object.hasOwn(EVENTS, String(value.hook_event_name)))
|
|
204
|
+
return { ok: false, error: "hook_event_name is required" };
|
|
205
|
+
const event = value.hook_event_name as CodexHookEvent;
|
|
206
|
+
const cwd = existingDirectory(value.cwd);
|
|
207
|
+
if (!cwd) return { ok: false, error: "cwd must be an existing absolute directory" };
|
|
208
|
+
const toolEvent = event === "PreToolUse" || event === "PostToolUse";
|
|
209
|
+
if (toolEvent && !nonEmpty(value.tool_name)) return { ok: false, error: "tool_name is required" };
|
|
210
|
+
const command = isRecord(value.tool_input) ? commandText(value.tool_input.command) : null;
|
|
211
|
+
if (toolEvent && isShellTool(value.tool_name) && !command)
|
|
212
|
+
return { ok: false, error: "tool_input.command is required for shell tools" };
|
|
213
|
+
if (event === "SubagentStart" && !nonEmpty(value.agent_id))
|
|
214
|
+
return { ok: false, error: "agent_id is required" };
|
|
215
|
+
return {
|
|
216
|
+
ok: true,
|
|
217
|
+
data: {
|
|
218
|
+
hook_event_name: event,
|
|
219
|
+
session_id: nonEmpty(value.session_id) ? value.session_id : "",
|
|
220
|
+
model: optionalText(value.model),
|
|
221
|
+
permission_mode: optionalText(value.permission_mode),
|
|
222
|
+
transcript_path: optionalText(value.transcript_path),
|
|
223
|
+
cwd,
|
|
224
|
+
// An unknown start source restores context but never offers history.
|
|
225
|
+
...(event === "SessionStart"
|
|
226
|
+
? { source: SOURCES.has(String(value.source)) ? (value.source as SessionSource) : "resume" }
|
|
227
|
+
: {}),
|
|
228
|
+
...(nonEmpty(value.turn_id) ? { turn_id: value.turn_id } : {}),
|
|
229
|
+
...(event === "UserPromptSubmit" ? { prompt: optionalText(value.prompt) } : {}),
|
|
230
|
+
...(nonEmpty(value.tool_name) ? { tool_name: value.tool_name } : {}),
|
|
231
|
+
...(toolEvent ? { tool_input: value.tool_input } : {}),
|
|
232
|
+
...(event === "PostToolUse" && typeof value.tool_response === "string"
|
|
233
|
+
? { tool_response: value.tool_response }
|
|
234
|
+
: {}),
|
|
235
|
+
...(command ? { command } : {}),
|
|
236
|
+
...(nonEmpty(value.tool_use_id) ? { tool_use_id: value.tool_use_id } : {}),
|
|
237
|
+
...(nonEmpty(value.agent_id) ? { agent_id: value.agent_id } : {}),
|
|
238
|
+
...(nonEmpty(value.agent_type) ? { agent_type: value.agent_type } : {}),
|
|
239
|
+
...(event === "Stop"
|
|
240
|
+
? {
|
|
241
|
+
last_assistant_message: optionalText(value.last_assistant_message),
|
|
242
|
+
stop_hook_active: value.stop_hook_active === true,
|
|
243
|
+
}
|
|
244
|
+
: {}),
|
|
245
|
+
...(event === "SubagentStop"
|
|
246
|
+
? {
|
|
247
|
+
agent_transcript_path: optionalText(value.agent_transcript_path),
|
|
248
|
+
last_assistant_message: optionalText(value.last_assistant_message),
|
|
249
|
+
stop_hook_active: value.stop_hook_active === true,
|
|
250
|
+
}
|
|
251
|
+
: {}),
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const protocolEvent = (input: CodexHookInput): HookEvent => {
|
|
257
|
+
switch (input.hook_event_name) {
|
|
258
|
+
case "SessionStart":
|
|
259
|
+
return { kind: "session.start", source: input.source! };
|
|
260
|
+
case "UserPromptSubmit":
|
|
261
|
+
return { kind: "context.turn", prompt: input.prompt ?? null };
|
|
262
|
+
case "PreToolUse": {
|
|
263
|
+
const toolUseId = input.tool_use_id ?? null;
|
|
264
|
+
if (isShellTool(input.tool_name))
|
|
265
|
+
return { kind: "shell.pre", command: input.command ?? "", toolUseId };
|
|
266
|
+
// apply_patch: the files are the patch body's `*** … File:` headers.
|
|
267
|
+
if (isWriteTool(input.tool_name))
|
|
268
|
+
return {
|
|
269
|
+
kind: "write.pre",
|
|
270
|
+
tool: input.tool_name!,
|
|
271
|
+
paths: writeTargets(input.tool_input),
|
|
272
|
+
toolUseId,
|
|
273
|
+
};
|
|
274
|
+
return { kind: "tool.pre", tool: input.tool_name!, toolUseId };
|
|
275
|
+
}
|
|
276
|
+
case "PostToolUse": {
|
|
277
|
+
const toolUseId = input.tool_use_id ?? null;
|
|
278
|
+
return isShellTool(input.tool_name)
|
|
279
|
+
? {
|
|
280
|
+
kind: "shell.post",
|
|
281
|
+
command: input.command ?? "",
|
|
282
|
+
stdout: input.tool_response ?? "",
|
|
283
|
+
// Codex reports the model-facing output, not an exit code.
|
|
284
|
+
exitCode: null,
|
|
285
|
+
toolUseId,
|
|
286
|
+
}
|
|
287
|
+
: // A non-shell tool result carries nothing workit observes.
|
|
288
|
+
{ kind: "tool.pre", tool: input.tool_name!, toolUseId };
|
|
289
|
+
}
|
|
290
|
+
case "SubagentStart":
|
|
291
|
+
return {
|
|
292
|
+
kind: "subagent.start",
|
|
293
|
+
agentId: input.agent_id!,
|
|
294
|
+
agentType: input.agent_type ?? "unknown",
|
|
295
|
+
task: null,
|
|
296
|
+
};
|
|
297
|
+
case "SubagentStop":
|
|
298
|
+
return {
|
|
299
|
+
kind: "subagent.stop",
|
|
300
|
+
agentId: input.agent_id ?? null,
|
|
301
|
+
agentType: input.agent_type ?? null,
|
|
302
|
+
lastMessage: input.last_assistant_message ?? null,
|
|
303
|
+
stopHookActive: input.stop_hook_active ?? false,
|
|
304
|
+
};
|
|
305
|
+
case "Stop":
|
|
306
|
+
return {
|
|
307
|
+
kind: "stop",
|
|
308
|
+
lastMessage: input.last_assistant_message ?? null,
|
|
309
|
+
stopHookActive: input.stop_hook_active ?? false,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const output = (event: string, extra: Record<string, unknown> = {}) => ({
|
|
315
|
+
hookSpecificOutput: { hookEventName: event, ...extra },
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
const render = (decision: HookDecision, native: string | null) => {
|
|
319
|
+
// stop.command.output takes no hookSpecificOutput: a block with a reason continues.
|
|
320
|
+
if (native === "Stop" && decision.kind === "continue")
|
|
321
|
+
return { json: { decision: "block", reason: decision.reason }, exitCode: 0 };
|
|
322
|
+
if (native === "SubagentStop" || native === "Stop" || native === null)
|
|
323
|
+
return { json: {}, exitCode: 0 };
|
|
324
|
+
if (decision.kind === "deny")
|
|
325
|
+
return {
|
|
326
|
+
json: output(native, {
|
|
327
|
+
permissionDecision: "deny",
|
|
328
|
+
permissionDecisionReason: decision.reason,
|
|
329
|
+
}),
|
|
330
|
+
exitCode: 0,
|
|
331
|
+
};
|
|
332
|
+
if (decision.kind === "context")
|
|
333
|
+
return { json: output(native, { additionalContext: decision.text }), exitCode: 0 };
|
|
334
|
+
return { json: output(native), exitCode: 0 };
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
export const codexAdapter: HostAdapter = {
|
|
338
|
+
descriptor: CODEX_DESCRIPTOR,
|
|
339
|
+
parse(raw, env) {
|
|
340
|
+
const host = detectCodexSurface(env);
|
|
341
|
+
const parsed = parseCodexHookInput(raw);
|
|
342
|
+
if (!parsed.ok) {
|
|
343
|
+
const native =
|
|
344
|
+
isRecord(raw) && Object.hasOwn(EVENTS, String(raw.hook_event_name))
|
|
345
|
+
? (raw.hook_event_name as CodexHookEvent)
|
|
346
|
+
: null;
|
|
347
|
+
return { ok: false, error: parsed.error, native, event: native ? EVENTS[native] : null };
|
|
348
|
+
}
|
|
349
|
+
const input = parsed.data;
|
|
350
|
+
return {
|
|
351
|
+
ok: true,
|
|
352
|
+
native: input.hook_event_name,
|
|
353
|
+
input: {
|
|
354
|
+
host,
|
|
355
|
+
cwd: input.cwd,
|
|
356
|
+
session: {
|
|
357
|
+
id: input.session_id,
|
|
358
|
+
agentId: input.agent_id ?? null,
|
|
359
|
+
agentType: input.agent_type ?? null,
|
|
360
|
+
parentId: null,
|
|
361
|
+
},
|
|
362
|
+
permissionMode: input.permission_mode,
|
|
363
|
+
transcriptPath: input.transcript_path,
|
|
364
|
+
model: input.model,
|
|
365
|
+
event: protocolEvent(input),
|
|
366
|
+
},
|
|
367
|
+
};
|
|
368
|
+
},
|
|
369
|
+
render,
|
|
370
|
+
addendum: (input) =>
|
|
371
|
+
`<workit-codex-mutations>Codex MCP is read-only: unattested callers cannot mutate. Run workit verbs with the workit CLI on the shell (node_modules/.bin/workit, or npx -y @workit-dev/cli): check, git branch|commit|push, pr, ci, stack, ledger, handoff; task-family mutations take --json${input.session.id ? ` --actor ${input.session.id}` : ""}. workit acts as this thread (CODEX_THREAD_ID${input.session.id ? `=${input.session.id}` : ""}) unless WORKIT_SESSION_ID is set. A verifier or reviewer records workit ledger verdict under its own session, never the author's: a workit-verifier or workit-reviewer agent uses the --session its SubagentStart context names; any other passes --as verifier (an id derived from its own thread). A hand-minted id such as <lead>-v1 is refused as forged_session. Merge and release need a workspace grant (workit grant show); a question answer is not host permission.</workit-codex-mutations>`,
|
|
372
|
+
};
|