Skip to content

T-4KYN-hooks-onto-shared-resolver-and-executor

State: closed/done · Impact: medium · Complexity: large

Give hook step lists the same per-layer provenance workflows now have, instead of runHooks reading the pre-merged config directly with no resolution step at all — and make runHooks execute each resolved step through the same primitive runWorkflow uses, instead of its own inline sh -c loop.

lib/services/hooks/run.ts both resolves (resolveVerb) and executes (exec/deps.sh.run(['-c', ...]), its own requireTrustedVerbs call) independently of step-runner.ts. runHooks reads layers.merged.hooks?.[hook] directly — the list loadConfigLayers already deep-merged, with no per-step provenance ever recorded.

A new resolveHook(hookName, projectRoot, opts): Promise<ResolvedHook> (mirroring chain.ts’s/workflows.ts’s one-module-per-section pattern) gives hook step lists the same {layer, mode} provenance via T-JIBO’s resolveLayeredSteps. step-runner.ts’s runWorkflow loop is refactored so its per-step-kind body becomes a standalone executeStep, called by both runWorkflow (unchanged behavior) and, new, runHooks — closing the “hooks have their own executor” gap in the same pass as the resolution gap.

  1. New lib/config/hooks.ts: resolveHook(hookName, projectRoot, opts): Promise<ResolvedHook>. Calls T-JIBO’s resolveLayeredSteps with no env layer and an empty defaultSteps (hooks have no built-in list), reading sdlc.local.yaml/sdlc.yaml’s raw hooks.<hookName> entries. A hook step’s {run: <name>} resolves through workflows.ts’s resolveWorkflow(<name>, ...) (T-4OUZ) and splices in its steps, with the existing “a hook step cannot be skill/prompt” rule now applied to the spliced-in steps too.
  2. lib/services/hooks/run.ts: replace the direct layers.merged.hooks?.[hook] read with a call to resolveHook; thread its per-step provenance into HookStepResult (a new optional source field) so a future doctor/config explain can show it.
  3. lib/services/apply/hooks.ts (the hooks applier): confirm it only needs the hook names present, not their resolved step content (shims call sdlc hooks run <hook> at commit time) — if so, no change beyond an import-path fix if it imported from verbs.ts.
  4. Extract the shared executor: step-runner.ts’s runWorkflow loop’s per-iteration body (skill/prompt → session launch; run → iterate resolvedVerb.verbs; script → substitute and run — each gated by requireTrustedVerbs first) becomes a standalone executeStep(resolvedStep, deps): Promise<StepOutcome>, called once per iteration by runWorkflow’s own loop (behavior-preserving refactor) and, new, by runHooks.
  5. lib/services/hooks/run.ts: after resolveHook returns a flattened run/script-only step list, replace the exec/deps.sh.run(['-c', text, hook, ...args], …) closure and inline requireTrustedVerbs call with one executeStep call per resolved leaf, keeping when/on_fail/ park as the wrapper around each call exactly as today.
AreaNote
solutions/ontological/lib/config/hooks.tsNew: resolveHook, mirroring chain.ts’s pattern
solutions/ontological/lib/config/hooks.test.tsNew
solutions/ontological/lib/services/orchestrator/step-runner.tsExtract executeStep from runWorkflow’s loop body; runWorkflow calls it unchanged in behavior
solutions/ontological/lib/services/hooks/run.tsRead resolveHook(...) instead of the merged config directly; execute via executeStep instead of its own exec/sh -c loop
solutions/ontological/lib/services/hooks/run.test.tsAdd a local-vs-project layering case; assert execution goes through executeStep
  • AC-1: A pre-commit hook step list set only in sdlc.yaml resolves with every step tagged {layer: 'project', mode: 'replace'}.
  • AC-2: The same hook name additionally {append: [...]}-set in sdlc.local.yaml resolves to the project list followed by the local-appended steps, the latter tagged {layer: 'local', mode: 'append'} — a case structurally impossible to observe correctly before this task (today’s deep-merge collapses local/project into one indistinguishable list before any step sees it).
  • AC-3: - run: setup inside a hook step resolves to setup workflow’s spliced script steps; - run: pr-review (whose target is a skill: step) fails resolution with the existing “a hook step cannot be a skill or prompt step” message.
  • AC-4: bun test solutions/ontological/lib/config/hooks.test.ts solutions/ontological/lib/services/hooks passes.
  • AC-5: bun test solutions/ontological/lib/services/orchestrator/step-runner.test.ts passes unchanged after the executeStep extraction — proving the refactor is behavior-preserving for runWorkflow’s existing callers.
  • AC-6: A failing run/script hook step’s trust-gate error message and exit behavior are byte-for-byte identical before and after this task (the gate call moved into executeStep, but its inputs/outputs did not change).
  • Any change to on_fail/when/park-marker behavior — untouched.

T-JIBO, T-4OUZ (hook run: steps splice workflow steps). Touches step-runner.ts, which T-Q65Q also edits (resolveEngine/ resolveHost) — land this task before T-Q65Q to avoid a rebase on the same file.

Drawn from D-LSLH-collapse-verbs-workflows-chain-hooks’s “Hooks execute through the shared primitive” section.

T-JIBO, T-4OUZ


← Back to Tasks