Skip to content

Deterministic, general extension points

Status: open/active

  • Upstream skills expose stable, general hook points but never know this repo’s SDLC shape; move stable prose into deterministic code and plug local behavior into the same extension system a downstream consumer would use.

This standard captures the architectural rule behind two related project principles:

  • use prose and Claude as a bootstrap harness only until behavior can move into deterministic code;
  • keep the distributed SDLC plugin general, even while this repo dogfoods it heavily.

The short version: upstream skills may expose stable hook points, but they must not know about this repo’s SDLC-specific development shape. This repo’s own special behavior must plug into the same extension system a downstream consumer would use.

Skill prose is allowed to discover workflow shape quickly. Once a step has a stable input, output, error mode, and testable behavior, it should move down into a script, schema, validator, or pure function. The skill then becomes a harness adapter that calls deterministic tools and uses the LLM only where judgment is still needed.

Stable prose wants to migrate when:

  • the same instruction appears in multiple skills;
  • a command output is parsed by prose;
  • a step can be expressed as “read files, decide, write files, exit”;
  • failures can be described by exit codes;
  • the behavior needs fixtures or regression tests.

The Claude Code plugin is the current harness. It is not the architecture boundary. Canonical state belongs in markdown, frontmatter, schemas, and project-local SDLC config. Deterministic tools should be callable from Claude, a future CLI, CI, or another agent harness with the same arguments and exit codes.

Claude-owned files belong under .claude/: settings, Claude hooks, project-local Claude skills, commands, agents, and Claude-specific scripts. SDLC-owned project-local runtime state and extensions belong under .sdlc/.

Anything shipped under solutions/ontological/ must make sense for any project that installs the SDLC plugin. Dogfooding can reveal general improvements, but it cannot justify hard-coded checks for this repository’s shape.

Extensions are contracts, not hidden prose

Section titled “Extensions are contracts, not hidden prose”

The default extension mechanism should be script-first. A hook point has a name, a path, inputs, output expectations, exit-code semantics, and a statement about whether it may mutate files. This makes the extension usable by humans, Claude, a future CLI, and CI.

Prose-shaped extensions should be treated as exceptional. If a future case truly needs “additional instructions” instead of a script, it should still have a formal contract: when it is loaded, what context it may read, what it may change, and how conflicts with the base workflow are resolved.

Self-development uses the same extension system

Section titled “Self-development uses the same extension system”

This repo does not get private development carve-outs in upstream skills. If SDLC-on-SDLC development needs an extra warning, check, or policy, it should live under this repo’s .sdlc/ extension tree or under this repo’s Claude-local .claude/ tooling if it is truly a Claude harness concern.

The existing .sdlc/skill-ext/task-work/step1-post.sh hook is the right pattern: the upstream task-work skill declares a general post-read hook, and this repo contributes the SDLC-specific solutions/ontological/skills/*/SKILL.md scan locally.

Use this layering as the design test for future changes:

  1. Data: markdown, frontmatter, schemas, templates, sdlc.yaml, and the compiled DAG definitions the engine executes.
  2. Deterministic tools: validators, scripts, migrations, classifiers, and query helpers with CLI contracts — reached from a registration as the engine’s built-in and shell steps, and from a shell or CI directly.
  3. Process registrations: the versioned declaration of how a kind: executes — readiness contract, step definitions, output contract, scheduling requirements, human-gate placement, and runner context. The registration declares the steps; the engine sequences them (D-VSLI-distributed-work-runner-architecture).
  4. Harness adapters: Claude Code today, a future CLI or other harnesses later. A SKILL.md is one — the head a harness loads, generated from or validated against a registration, never where procedure truth lives.

A change belongs in the lowest layer that can own it honestly. Sequencing is layer 3 and belongs to a registration; prose that sequences is a layer-4 artifact standing in for one that has not been written yet.

2. Add machine-readable extension point descriptors

Section titled “2. Add machine-readable extension point descriptors”

Keep the current .sdlc/skill-ext/<skill>/<event>.sh shape, but add a small descriptor beside each skill when a skill exposes hooks. For example:

version: 1
skill: task-work
events:
- id: after-task-read
hook: .sdlc/skill-ext/task-work/after-task-read.sh
when: after the task file is resolved and read
inputs:
env:
TASK_FILE: absolute path to the resolved task file
PROJECT_ROOT: absolute path to the consuming project root
argv: []
exit: informational
may_mutate: false

SKILL.md can still describe the hook for human readers, but the descriptor becomes the source for docs generation, CLI discovery, and contract tests. Existing numbered events such as step1-post can remain for compatibility, but new events should prefer semantic names such as after-task-read so renumbering prose does not break a hook contract.

Re-key from the skill to the process at cutover. Both the skill: key and the “after step N” anchor assume a SKILL.md is what runs the steps. Once a process registration owns the step definitions and the engine executes them (D-VSLI-distributed-work-runner-architecture), a skill step position has no referent. The descriptor then keys on process: — with kind: naming the routing key it is registered under — and anchors each event to a registered step boundary:

version: 1
process: implementation
kind: implementation
events:
- id: after-work-order-read
step: read-work-order
position: after
hook: .sdlc/skill-ext/implementation/after-work-order-read.sh
exit: informational
may_mutate: false

The on-disk shape does not move: hooks stay at .sdlc/skill-ext/<slug>/<event>.sh, so every hook a consuming project has already installed keeps firing. Only the descriptor’s key and the anchor change — and the anchor gets more stable, since a registered step id survives edits that renumbered prose never did.

3. Treat script hooks as the default, prose hooks as a later design

Section titled “3. Treat script hooks as the default, prose hooks as a later design”

For now, keep extensions script-first. A script hook can be tested, versioned, made executable, and called from any harness.

If a real downstream use case needs an additional prose instruction, add it deliberately rather than by implication. A possible future shape:

prose_extensions:
- id: task-definition-advisory
path: .sdlc/skill-ext/task-define/task-definition-advisory.md
loaded_at: before gap questions are generated
allowed_effect: advisory-only

That mechanism should not land until there is a concrete case that a script hook cannot handle.

Add deterministic support commands before hook usage spreads:

  • list extension points exposed by the installed plugin;
  • list hooks installed in the current project;
  • validate that installed hooks are executable;
  • run an individual hook with fixture inputs;
  • scaffold .sdlc/skill-ext/<skill>/<event>.sh and update .gitignore idempotently.

These can be scripts first and later surfaced by setup or a CLI.

5. Define a migration rule for prose-to-code extraction

Section titled “5. Define a migration rule for prose-to-code extraction”

When a skill step becomes stable, extract it in this order:

  1. Write the contract: inputs, outputs, exit codes, and mutation scope.
  2. Add fixtures for the current prose behavior.
  3. Classify what stabilized — a sequence or a leaf. If what settled is a step boundary or a sequence — the order steps run in, what makes one ready, where a human gate sits, what happens on failure — it becomes a process registration: declare the steps and let the engine sequence them (layer 3 above). If what settled is a leaf action — one call with an input, an output, and an exit code — it becomes a registry op. Most stable prose contains both; split it before extracting either.
  4. Move the deterministic body into solutions/ontological/lib/ as a registry op (with the entity it acts on, or under services/ / util/) — or, for a genuinely skill-private helper, a co-located script.
  5. Replace the SKILL.md section with “call this op / script, interpret these statuses” — or, for a sequence, delete it: the registration is the procedure, and the head is generated from or validated against it.
  6. For a skill-private helper, promote it to a shared home only when a second real caller appears; a capability is placed in solutions/ontological/lib/ from the start.

Amended 2026-06-02 per D-0007-deterministic-op-substrate: the original ordering deferred all promotion to a second caller, matching S-0001-co-locate-first-promote-when-shared. That co-locate-first rule now applies to skill-private helpers only; capabilities are entity-placed in solutions/ontological/lib/ immediately (and exposed via a generated adapter). This still prevents premature shared APIs for the helper case.

6. Route SDLC dogfood findings through a decision gate

Section titled “6. Route SDLC dogfood findings through a decision gate”

Before a dogfood follow-up edits solutions/ontological/, ask:

  • Would this behavior help a non-SDLC consuming project with the same workflow?
  • Does it reference this repo’s solutions/ontological/, sites/, .claude/, or .sdlc/ layout?
  • Could the behavior be expressed as a hook under .sdlc/skill-ext/?
  • Could the generic part be an upstream extension point and the specific behavior be a local hook?

Only the generic extension point belongs upstream. The SDLC-specific implementation belongs locally.

Bad upstream patterns inside solutions/ontological/:

  • detecting solutions/ontological/skills/*/SKILL.md in a general workflow and adding behavior only useful when developing the SDLC plugin itself;
  • special-casing solutions/ontological/lib/model/entities/ or docs/planning/tasks/ in a way that assumes the consumer repo is this repo;
  • adding prose that tells downstream projects how to handle this repo’s own runtime/install quirks.

Good upstream patterns:

  • expose a named extension point with a documented contract;
  • move mechanical behavior into a script with fixtures;
  • route project-specific behavior to .sdlc/skill-ext/...;
  • keep absent hooks as no-ops.

Implementation status against this standard — specific violations, in-flight migrations, and near-term extraction targets — lives in task entities under docs/planning/tasks/, not in this file. The standard captures the durable rule; the tasks track the fixes.


← Back to Standards