Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions claude-skills/feature/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ in the current project directory when needed, then creates FluencyLoop's state.
continuing. Only stop if `init` itself fails. Do not hand-create `.fluencyloop`, `docs`, or
`.claude/skills`.

**Reattach a safe detached checkout.** If `branch` is `HEAD`, `state_matches_branch` is `true`,
and `state_branch` is nonempty, this is the exact recorded feature tip, not a split. Run `git
status --porcelain`. If it is empty, run `git checkout <state_branch>`, then rerun the bundled
`fluencyloop check --json` before any work. If it is not empty, stop and state that the detached
checkout has changes which must be resolved before it can be reattached. Do not write records or
start a new feature while detached. `check` deliberately treats only the exact branch tip this
way; any other detached commit remains a split.

**Refuse split state.** Parse `state_matches_branch` from the bundled `fluencyloop check --json`
before any calibration, feature declaration, session, or writer command. If it is `false`, stop
immediately: state the checkout branch and `state_branch`, and ask the developer which existing
Expand Down Expand Up @@ -356,8 +364,11 @@ Build the feature one **meaningful slice** at a time (a logical, commit-worthy c
`documented`; use `follow-up` only when appropriate. Separate it from decisions: a role you
explained is knowledge transfer even if no fork was chosen. **About the work, never the
person** — no competence, prior knowledge, or "who learned what" (committed files, GDPR); the
per-developer picture lives only in the calibration profile. Escape a literal `|` as `\|` and a
literal backslash as `\\`.
per-developer picture lives only in the calibration profile. Compose and validate every value
before the one batched call: each `--component` has exactly three or four **nonempty**
pipe-separated fields and each `--gotcha` has exactly two **nonempty** fields. Omit a field
instead of emitting `||`. Escape a literal `|` as `\|` and a literal backslash as `\\`. Do not
run incomplete, bare, or trial `fluencyloop knowledge` commands to discover this syntax.
- **Decisions** *(the script formats them — you supply only the field values)* — for each, run
`fluencyloop decision` so the block is assembled deterministically; never hand-write the
bullet schema:
Expand Down Expand Up @@ -451,6 +462,14 @@ and the store records already carry that detail. Read `fluencyloop calibration s
at distillation time, and pitch the prose to the developer's recorded level in the relevant
domain:

**Make the coverage decision explicit.** Re-read the feature's completed slices before writing.
If they establish or change a product idea that a new joiner needs to hold, append its
architectural record with one to three useful tags and create its explanation. If they establish
the project's initial product shape or materially change its problem, shape, or major flow,
refresh `product.md`. The first material feature is never an excuse to leave both blank. A wholly
local feature may omit those two artifacts, but say why in the hand-off rather than silently
skipping the decision.

- `fluent` / `familiar`: concise product-level prose; name the load-bearing change without
re-teaching fundamentals.
- `learning` / `new`: explain the terms, flow, and product consequence needed to hold the
Expand Down
3 changes: 3 additions & 0 deletions plugins/fluencyloop/scripts/bash/calibration.sh
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ reset_signals() {
SUB="${1:-show}"; shift || true

case "$SUB" in
help|-h|--help)
echo "Usage: fluencyloop calibration [init | show [--json] | edit | signal <dim> <wave|deeper|correct> | compact [--dry-run]]"
;;
init)
if [ -f "$CAL" ]; then echo "Calibration profile already exists: $CAL"
else seed; echo "Created calibration profile: $CAL"; fi
Expand Down
15 changes: 13 additions & 2 deletions plugins/fluencyloop/scripts/bash/check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,18 @@ STAGE="$(state_get stage)"
BASE="$(state_get base_ref)"; [ -z "$BASE" ] && BASE="main"
LAST_SESSION="$(state_get last_session)"
STATE_MATCHES_BRANCH=true
DETACHED_HEAD=false
[ "$BRANCH" = "HEAD" ] && DETACHED_HEAD=true
if [ -n "$STATE_BRANCH" ] && [ -n "$BRANCH" ] && [ "$STATE_BRANCH" != "$BRANCH" ]; then
STATE_MATCHES_BRANCH=false
# A detached checkout at the recorded branch tip is a recoverable transport state (for
# example after an agent has checked out a commit). It is not a conflicting feature context.
# Do not accept an arbitrary ancestor: attaching to it could silently discard newer branch work.
if $DETACHED_HEAD && git show-ref --verify --quiet "refs/heads/$STATE_BRANCH" \
&& [ "$(git rev-parse HEAD 2>/dev/null || true)" = "$(git rev-parse "$STATE_BRANCH" 2>/dev/null || true)" ]; then
: # The feature skill reattaches this exact, clean branch tip before it writes work.
else
STATE_MATCHES_BRANCH=false
fi
fi

# --- un-journaled drift: commits since the last committed session record. Before the first
Expand Down Expand Up @@ -249,12 +259,13 @@ if [ -n "$STORE_ROOT" ] && [ -d "$STORE_ROOT" ]; then
fi

if $JSON_MODE; then
printf '{"git_repo":%s,"fluency":%s,"legacy_imported_features":%s,"legacy_migration_pending":%s,"branch":"%s","state_branch":"%s","state_matches_branch":%s,"feature":"%s","stage":"%s","base_ref":"%s","last_session":"%s","unjournaled_commits":%s,"calibration":%s,"node":%s,"constitution":"%s","store_errors":[%s]}\n' \
printf '{"git_repo":%s,"fluency":%s,"legacy_imported_features":%s,"legacy_migration_pending":%s,"branch":"%s","detached_head":%s,"state_branch":"%s","state_matches_branch":%s,"feature":"%s","stage":"%s","base_ref":"%s","last_session":"%s","unjournaled_commits":%s,"calibration":%s,"node":%s,"constitution":"%s","store_errors":[%s]}\n' \
"$IN_GIT_REPO" \
"$FLUENCY_PRESENT" \
"$LEGACY_IMPORTED_FEATURES" \
"$LEGACY_MIGRATION_PENDING" \
"$(json_escape "$BRANCH")" \
"$DETACHED_HEAD" \
"$(json_escape "$STATE_BRANCH")" \
"$STATE_MATCHES_BRANCH" \
"$(json_escape "$FEATURE")" \
Expand Down
3 changes: 3 additions & 0 deletions plugins/fluencyloop/scripts/powershell/calibration.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ $rest = @()
if ($args.Count -gt 1) { $rest = @($args[1..($args.Count - 1)]) }

switch ($sub) {
'help' { FlOut 'Usage: fluencyloop calibration [init | show [--json] | edit | signal <dim> <wave|deeper|correct> | compact [--dry-run]]' }
'-h' { FlOut 'Usage: fluencyloop calibration [init | show [--json] | edit | signal <dim> <wave|deeper|correct> | compact [--dry-run]]' }
'--help' { FlOut 'Usage: fluencyloop calibration [init | show [--json] | edit | signal <dim> <wave|deeper|correct> | compact [--dry-run]]' }
'init' {
if (Test-Path -LiteralPath $CAL) { FlOut "Calibration profile already exists: $CAL" }
else { Seed; FlOut "Created calibration profile: $CAL" }
Expand Down
15 changes: 15 additions & 0 deletions plugins/fluencyloop/scripts/powershell/check.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,20 @@ $stage = FlStateGet 'stage'
$base = FlStateGet 'base_ref'; if (-not $base) { $base = 'main' }
$lastSession = FlStateGet 'last_session'
$stateMatchesBranch = (-not $stateBranch) -or (-not $branch) -or ($stateBranch -eq $branch)
$detachedHead = $branch -eq 'HEAD'
if (-not $stateMatchesBranch -and $detachedHead) {
# Treat only the exact recorded branch tip as recoverable. An arbitrary ancestor could be an
# intentional historical checkout, so it remains a real split state requiring user direction.
& git show-ref --verify --quiet "refs/heads/$stateBranch" *> $null
if ($LASTEXITCODE -eq 0) {
$headCommit = & git rev-parse HEAD 2>$null
$stateCommit = & git rev-parse $stateBranch 2>$null
if ($LASTEXITCODE -eq 0 -and $headCommit -and $stateCommit -and
(($headCommit | Select-Object -First 1) -eq ($stateCommit | Select-Object -First 1))) {
$stateMatchesBranch = $true
}
}
}

# Un-journaled drift: commits since the last committed session record; else since base. Legacy
# session markdown remains a read-only fallback for projects that have not imported it yet.
Expand Down Expand Up @@ -188,6 +202,7 @@ if ($jsonMode) {
',"legacy_imported_features":' + $legacyImportedFeatures +
',"legacy_migration_pending":' + $legacyMigrationPending.ToString().ToLowerInvariant() +
',"branch":"' + (FlJsonEscape $branch) + '"' +
',"detached_head":' + $detachedHead.ToString().ToLowerInvariant() +
',"state_branch":"' + (FlJsonEscape $stateBranch) + '"' +
',"state_matches_branch":' + $stateMatchesBranch.ToString().ToLowerInvariant() +
',"feature":"' + (FlJsonEscape $feature) + '"' +
Expand Down
23 changes: 21 additions & 2 deletions plugins/fluencyloop/skills/feature/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ For that `fluencyloop init --json` command in Codex, request sandbox elevation b
execution. It may create or update Codex-protected `.git` metadata; do not first attempt it in the
standard sandbox.

**Reattach a safe detached checkout.** If `branch` is `HEAD`, `state_matches_branch` is `true`,
and `state_branch` is nonempty, this is the exact recorded feature tip, not a split. Run `git
status --porcelain`. If it is empty, run `git checkout <state_branch>`, then rerun `fluencyloop
check --json` before any work. If it is not empty, stop and state that the detached checkout has
changes which must be resolved before it can be reattached. Do not write records or start a new
feature while detached. `check` deliberately treats only the exact branch tip this way; any other
detached commit remains a split.

**Refuse split state.** Parse `state_matches_branch` from `fluencyloop check --json` before any
calibration, feature declaration, session, or writer command. If it is `false`, stop immediately:
state the checkout branch and `state_branch`, and ask the developer which existing feature context
Expand Down Expand Up @@ -413,8 +421,11 @@ visible; the journal is its durable byproduct.
`documented`; use `follow-up` only when appropriate. Separate it from decisions: a role you
explained is knowledge transfer even if no fork was chosen. **About the work, never the
person** — no competence, prior knowledge, or "who learned what" (committed files, GDPR); the
per-developer picture lives only in the calibration profile. Escape a literal `|` as `\|` and a
literal backslash as `\\`.
per-developer picture lives only in the calibration profile. Compose and validate every value
before the one batched call: each `--component` has exactly three or four **nonempty**
pipe-separated fields and each `--gotcha` has exactly two **nonempty** fields. Omit a field
instead of emitting `||`. Escape a literal `|` as `\|` and a literal backslash as `\\`. Do not
run incomplete, bare, or trial `fluencyloop knowledge` commands to discover this syntax.
- **Decisions** *(the script formats them — you supply only the field values)* — for each, run
`fluencyloop decision` so the block is assembled deterministically; never hand-write the
bullet schema:
Expand Down Expand Up @@ -506,6 +517,14 @@ and the store records already carry that detail. Read `fluencyloop calibration s
at distillation time, and pitch the prose to the developer's recorded level in the relevant
domain:

**Make the coverage decision explicit.** Re-read the feature's completed slices before writing.
If they establish or change a product idea that a new joiner needs to hold, append its
architectural record with one to three useful tags and create its explanation. If they establish
the project's initial product shape or materially change its problem, shape, or major flow,
refresh `product.md`. The first material feature is never an excuse to leave both blank. A wholly
local feature may omit those two artifacts, but say why in the hand-off rather than silently
skipping the decision.

- `fluent` / `familiar`: concise product-level prose; name the load-bearing change without
re-teaching fundamentals.
- `learning` / `new`: explain the terms, flow, and product consequence needed to hold the
Expand Down
6 changes: 6 additions & 0 deletions tests/calibration.bats
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ assert "bogus" not in d, d # invalid level excluded
[ "$status" -ne 0 ]
}

@test "--help prints calibration usage successfully" {
run cal --help
[ "$status" -eq 0 ]
[[ "$output" == *"Usage: fluencyloop calibration"* ]]
}

# --- signals + compact (demonstrated-engagement adaptation) ---------------

level_of() { cal show --json | python3 -c "import json,sys;print(json.load(sys.stdin).get('$1',''))"; }
Expand Down
12 changes: 12 additions & 0 deletions tests/check.bats
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ load test_helper
[ "$(echo "$output" | json_field state_branch)" = "feature/001-add-search" ]
}

@test "check accepts a detached checkout at the recorded feature tip" {
setup_initialized_repo
bash "$BIN/new-feature.sh" "add search" >/dev/null
git checkout -q --detach HEAD

run bash "$BIN/check.sh" --json
[ "$status" -eq 0 ]
[ "$(echo "$output" | json_field branch)" = "HEAD" ]
[ "$(echo "$output" | json_field detached_head)" = "True" ] || [ "$(echo "$output" | json_field detached_head)" = "true" ]
[ "$(echo "$output" | json_field state_matches_branch)" = "True" ] || [ "$(echo "$output" | json_field state_matches_branch)" = "true" ]
}

@test "check: constitution states - present and pointer" {
setup_initialized_repo
printf '# Constitution\n\n## Principles\n\n### §1 — no sync calls in the request path\n' \
Expand Down
8 changes: 8 additions & 0 deletions tests/plugin.bats
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ feature_text = read_text(root / "claude-skills" / "feature" / "SKILL.md")
assert "**Refuse split state.**" in feature_text
assert "state_matches_branch" in feature_text
assert "Do not create a new feature, switch branches, or overwrite state" in feature_text
assert "Reattach a safe detached checkout." in feature_text
assert "git checkout <state_branch>" in feature_text
assert "Resume preconditions after the answer." in feature_text
assert "Immediately rerun `fluencyloop check --json`" in feature_text
assert "**Migrate imported history before normal feature work.**" in feature_text
Expand All @@ -117,6 +119,8 @@ codex_feature_text = read_text(dist / "skills" / "feature" / "SKILL.md")
assert "**Refuse split state.**" in codex_feature_text
assert "state_matches_branch" in codex_feature_text
assert "Do not create a new feature, switch branches, or overwrite state" in codex_feature_text
assert "Reattach a safe detached checkout." in codex_feature_text
assert "git checkout <state_branch>" in codex_feature_text
assert "Resume preconditions after the answer." in codex_feature_text
assert "Immediately rerun `fluencyloop check --json`" in codex_feature_text
assert "complete the mandatory migration before calibration, preferences, ticket numbering" in codex_feature_text
Expand Down Expand Up @@ -146,6 +150,10 @@ assert "topic-specific question" in codex_feature_text
assert "standalone comprehension question" not in codex_feature_text
assert "do not run another implementation" in codex_feature_text
for feature_skill_text in [feature_text, codex_feature_text]:
assert "exactly three or four **nonempty**" in feature_skill_text
assert "Do not\n run incomplete, bare, or trial `fluencyloop knowledge`" in feature_skill_text
assert "Make the coverage decision explicit." in feature_skill_text
assert "The first material feature is never an excuse" in feature_skill_text
assert "Understanding checks are self-report, never quizzes" in feature_skill_text
assert "Do you understand this explanation, or should I clarify anything?" in feature_skill_text
assert 'explaining it "in your own' in feature_skill_text
Expand Down
5 changes: 5 additions & 0 deletions tests/powershell/calibration.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ Describe 'calibration.ps1' {
(Invoke-FlExit 'calibration.ps1' 'frobnicate') | Should -Not -Be 0
}

It '--help prints calibration usage successfully' {
(Invoke-FlExit 'calibration.ps1' '--help') | Should -Be 0
(Invoke-Fl 'calibration.ps1' '--help') | Should -Match 'Usage: fluencyloop calibration'
}

It 'signal appends to the ledger; a calibration level is rejected as a signal' {
& $script:PwshExe -NoProfile -File "$script:Bin/calibration.ps1" 'init' | Out-Null
& $script:PwshExe -NoProfile -File "$script:Bin/calibration.ps1" 'signal' 'java' 'wave' | Out-Null
Expand Down
11 changes: 11 additions & 0 deletions tests/powershell/check.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ Describe 'check.ps1' {
$j.state_branch | Should -Be 'feature/001-add-search'
}

It 'accepts a detached checkout at the recorded feature tip' {
$script:repo = Initialize-TestRepo
& $script:PwshExe -NoProfile -File "$script:Bin/new-feature.ps1" 'add search' | Out-Null
git checkout -q --detach HEAD
(Invoke-FlExit 'check.ps1' '--json') | Should -Be 0
$j = Get-FlJson 'check.ps1' '--json'
$j.branch | Should -Be 'HEAD'
$j.detached_head | Should -BeTrue
$j.state_matches_branch | Should -BeTrue
}

It 'constitution states: present and pointer' {
$script:repo = Initialize-TestRepo
$c = "$script:repo/docs/fluencyloop/constitution.md"
Expand Down
Loading