hns-lsel-applier — LSEL APPLY engine

SkillDev tools

Local Self-Evolution Loop (LSEL) APPLY engine — the playback-only consumer of approved decision.json records that drives `.moai/hooks/lsel-apply.sh` for the GOOS-local PROPOSE→APPLY seam closure (SPEC-LSEL-LOCAL-EVOLUTION-001 M3). Reads an approved decision.json, validates the target against the frozen allowlist (.claude/lsel/frozen-allowlist.json), mechanically refuses execution-meta targets lacking a synchronous-approval marker, applies the referenced diff.patch, appends an apply-ledger.jsonl row, and commits one lsel-<proposal-id>-tagged Conventional Commit on the feature branch. M3 scope: APPLY bypass closure only (the frozen Go applier stays frozen — its write-flag at internal/harness/applier.go:22 stays false; REQ-LSEL-003).

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the hns-lsel-applier — LSEL APPLY engine skill

What this skill tells your AI

The instructions your AI receives, as published by modu-ai/moai-adk in .claude/skills/hns-lsel-applier/SKILL.md and read by ahel’s review.

Namespace: hns-lsel-* is user-owned dogfood (CLAUDE.local.md §24). This skill is NOT mirrored into internal/template/templates/ — it lives only in this repo. Graduation to moai-lsel-* + 16-language distribution is a separate SPEC (out of scope per spec.md §G).

Token discipline. This body deliberately avoids the literal identifier of the frozen Go applier's write-flag and the literal name of the orchestrator-only user-question channel. Both are referenced by location and role (e.g. "the write-flag at internal/harness/applier.go:22", "the orchestrator's synchronous user gate") so the REQ-LSEL-003 invariant-grep and the E4 subagent-boundary grep over LSEL surfaces read zero literal matches. The invariants themselves are stated in plain English.

What this skill does

The LSEL loop's PROPOSE→APPLY seam was dead in production: Applier.Apply never ran (manifest.jsonl absent), CuratorDispatch had 0 production callers, and the frozen Go applier's write-flag at internal/harness/applier.go:22 (kept false) is the apply dead-switch (REQ-LSEL-003 — M3 keeps it false by BYPASS, never unfreeze). The M3 closure routes APPLY through a parallel user-owned applier that writes only to the six evolvable surfaces (spec.md §B.3), while the frozen Go applier stays byte-for-byte frozen. Design SSOT: .moai/reports/moai-local-self-evolution-design-20260804.html §4 ("동결된 Go applier를 우회하는 병렬 사용자 소유 applier를 세운다" — build a parallel user-owned applier that bypasses the frozen Go one) + §7 (the two-round critique moved the allowlist OUT of the evolvable skill files into a frozen meta file, and added execution-meta files to the forced-gate set).

The APPLY engine is the playback hook .moai/hooks/lsel-apply.sh plus this skill's model-mediated judgment (target validation nuance, approval-marker provenance, blast- radius reasoning the mechanical hook cannot see). The hook is the load-bearing safety floor; this skill is the consumer-facing surface that decides WHICH approved decision to feed it next and how to interpret refusal.

The APPLY pipeline

lsel-apply.sh <decision.json> performs five steps. Steps 1-2 are the safety floor; steps 3-5 are the playback.

  1. Frozen-allowlist hard-reject (REQ-LSEL-001 / AC-LSEL-001). The target path in decision.json is matched against the frozen_patterns regex list in .claude/lsel/frozen-allowlist.json. A match → REFUSE: a row is appended to .moai/logs/lsel-reject.log naming the rejected path and category frozen-path, NO file is written, and the hook exits 2.
  2. Execution-meta forced-gate (REQ-LSEL-002 / AC-LSEL-005 D3 self-amending- handcuffs). If the target matches one of the four execution_meta categories — (i) the frozen allowlist meta file itself, (ii) an applier/curator skill body (hns-lsel-applier/, hns-lsel-curator/), (iii) the apply hook (.moai/hooks/lsel-*.sh), (iv) the settings.local.json hook-registration subblock — the hook checks decision.json for a synchronous-approval marker (a synchronous_approval object with decision: "approved", produced by the orchestrator's synchronous user gate). No marker → REFUSE: reject-log row with category execution-meta, exit 3, no write. With marker → proceeds (the refusal is keyed on the absent marker, NOT on the category match alone). The refusal semantics mirror the M2 csa_refusal_test.sh fixture exactly.
  3. Apply the diff.patch via git apply (playback of an already-approved decision). Only the paths declared in the patch are staged — never git add -A (working-tree hygiene).
  4. Append the apply-ledger row to .moai/state/lsel/apply-ledger.jsonl: {proposal_id, target_surface, ts, result:"applied", commit_sha, category}. This is the manifest the frozen Go applier never produced, finally real in user-owned space.
  5. Commit the staged change as ONE feat(lsel-<proposal-id>): ... Conventional Commit on the current (feature) branch (REQ-LSEL-004). The ledger row's commit_sha is backfilled from the commit's short SHA.

A no-arg invocation is a clean no-op (exit 0) so an empty approved-queue does not derail a loop pass.

The model-mediated layer (you, when invoked)

When this skill is invoked to drive an APPLY pass, your job on top of the mechanical hook is:

  • Read the proposal's proposal.md + self-critique.md at .moai/state/lsel/proposals/<id>/. A proposal with status: blocked (any UNRESOLVED self-critique objection) MUST NOT be fed to the hook — return a blocker report instead.
  • Confirm the approval marker's provenance. The synchronous_approval object must carry a real orchestrator-produced approval artifact (the synchronous user-question channel the orchestrator owns per CLAUDE.md §8). A marker the loop fabricated for itself is the self-amending-handcuffs failure mode (REQ-LSEL-002); the hook's mechanical check is the floor, your provenance judgment is the ceiling. This skill is a subagent mechanism and NEVER invokes the orchestrator-only user-question channel; return a blocker report and let the orchestrator run the gate.
  • Re-verify the frozen-allowlist invariant by grepping the write-flag at internal/harness/applier.go:22 — it MUST stay false. If M3's bypass ever drifts toward unfreezing the Go applier, return a blocker (AP-LSEL-002).

What this skill does NOT do

  • No unfreezing of the Go applier — the write-flag at internal/harness/applier.go:22 stays false (REQ-LSEL-003). The bypass is parallel and user-owned; the frozen applier is reference-only.
  • No edits to frozen doctrineinternal/template/templates/**, .claude/rules/moai/**, CLAUDE.md, retained agents, moai-* skills, the frozen Go applier / curator_dispatch.go, and .moai/config/sections/** are byte-for-byte untouched. The allowlist hard-rejects them (step 1).
  • No new-apply / self-approve / allowlist-amend primitive in lsel-apply.sh (REQ-LSEL-008). The hook only consumes already-approved decisions; it does not author proposals or bless them. The M4 verify.sh runs only AFTER an apply has committed — it does not create a new apply; it never self-approves.
  • No user-question invocation — subagent boundary (CLAUDE.md §8). Return a blocker report; the orchestrator runs the synchronous gate.

VERIFY stage (M4 — REQ-LSEL-013 / AC-LSEL-015)

After an apply commits a proposal, VERIFY proves the apply was safe. VERIFY has two layers — both MANDATORY:

(a) Mechanical layer — verify.sh (bash-testable)

verify.sh --proposal-dir <p> --repo-root <r> [--timeout SECS] [--feedback-file <f>] runs the proposal's frontmatter verify_command with a timeout-retry-once policy and auto-reverts on a second failure:

  • 2-attempt policy. Attempt 1 runs. On SUCCESS → verified:true, stop. On TIMEOUT-class failure → attempt 2 (flaky tolerance — report §10 P3: "retry once on timeout so a correct proposal isn't flipped by noise"). On NON-TIMEOUT-FAIL → attempt 2 (allows the "second non-timeout failure" of AC-LSEL-015 clause 3 to materialize before revert fires).
  • Attempt 2: SUCCESS → verified:true. Any failure (timeout OR non-timeout) → verified:false + auto git revert lsel-<proposal-id> + the proposal's feedback_*.md is marked verified: false.
  • The outcome is appended to the apply ledger as a {"stage":"verify","verified":true|false,...} row — the load-bearing signal.

(b) MANDATORY /moai gate superset (model-mediated)

/moai gate (lint+format+type+test) is MANDATORY after every apply — it is NOT optional. A bash hook cannot invoke a Claude Code slash command directly, so the gate runs MODEL-SIDE: when this skill drives an APPLY pass, the orchestrator/model runs /moai gate after the mechanical apply + verify. The proposer-authored verify_command alone is circular (report §11 mustFix B#6 / AP-LSEL-004): a proposer can author a verify_command that its own change satisfies. /moai gate is the independent check — lint, format, type-check, and the test suite exercise surfaces the proposal never touched. Treat a mechanical verify_command PASS without a green /moai gate as an unverified apply.

Model-mediated layer (you, when invoked)

  • After lsel-apply.sh commits and verify.sh reports, run /moai gate (the MANDATORY superset). If the gate fails, the apply is unverified even if verify.sh reported verified:true — treat it as a VERIFY failure and revert.
  • A revert (whether auto-fired by verify.sh or gate-driven) MUST be surfaced to the orchestrator as a blocker report — the orchestrator, not this skill, confirms a revert with the user. Never run /moai gate's user-facing surface from inside this subagent mechanism; return a blocker report.

Verification (run before declaring an APPLY+VERIFY pass complete)

# M4 VERIFY characterization test (AC-LSEL-015) — hermetic temp repo:
.claude/skills/hns-lsel-applier/verify_test.sh

# the verify ledger row carries the outcome:
grep '"stage":"verify"' .moai/state/lsel/apply-ledger.jsonl

Verification (run before declaring an APPLY pass complete)

# 1. APPLY characterization test (AC-001/002/005/008/013) — hermetic temp repo:
.claude/skills/hns-lsel-applier/apply_test.sh

# 2. Rollback-rehearsal SHIP GATE (AC-014):
.claude/skills/hns-lsel-applier/rollback_rehearsal_test.sh

# 3. REQ-LSEL-003 frozen-flag re-verify (post-apply). The frozen Go applier's
#    write-flag at internal/harness/applier.go:22 MUST read `false`, and no LSEL
#    surface (skills/hooks/state/allowlist) references that identifier as mutable.
#    (The literal identifier is intentionally not written here; find it at line 22
#    of the frozen file and grep LSEL surfaces for it — expect zero hits.)
sed -n '22p' internal/harness/applier.go   # MUST show the `= false` write-flag line

# 4. The apply ledger carries the new row:
tail -1 .moai/state/lsel/apply-ledger.jsonl

Cross-references

  • SPEC: .moai/specs/SPEC-LSEL-LOCAL-EVOLUTION-001/{spec,plan,acceptance,progress}.md
  • Design report (SSOT): .moai/reports/moai-local-self-evolution-design-20260804.html §4 (user-owned parallel applier), §7 (allowlist relocation + execution-meta forced gate), §10 P3, §11 mustFix A#1/A#5/A#8.
  • Frozen applier (reference only): internal/harness/applier.go:22 (the frozen write-flag), internal/harness/curator_dispatch.go.
  • Curator (PROPOSE stage, M2): .claude/skills/hns-lsel-curator/SKILL.md (CSA forced-gate doctrine + the Tier-4 DEAD finding).
  • CSA refusal fixture: .claude/skills/hns-lsel-curator/csa_refusal_test.sh (the refusal rule this hook enforces mechanically).
  • Namespace guard: internal/template/split_namespace_test.go, internal/template/internal_content_leak_test.go (must NOT flag a hns-lsel-applier leak — this skill is dogfood, never templated).

Signals

GitHub stars
1k
Forks
221
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
hns-lsel-applier
Source
github.com/modu-ai/moai-adk