Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
f8ea037
add git-ai tool page, uninstall guide and the uninstall-hooks finding
ftschindler Oct 9, 2026
8162054
Merge remote-tracking branch 'origin/main' into staging
ftschindler Oct 9, 2026
6598456
add puppeteer tool page and the finding its unpinned version produced
ftschindler Oct 9, 2026
8890818
add exploration, tracking AI authorship with git-ai
ftschindler Oct 9, 2026
b9774f9
Pin federated-knowledge-skills to v0.2.2
ftschindler Oct 9, 2026
3451632
Extend the SHA-pinning principle to reusable workflows
ftschindler Oct 10, 2026
bb4c026
Add the browser-pinning guide, and pin puppeteer in this repository's…
ftschindler Oct 11, 2026
344d34c
Add the ruleset bypass finding, and link the hook comment to its guide
ftschindler Oct 12, 2026
d02ad35
Write up sharing one bundle on two branches, and the principle under it
ftschindler Oct 14, 2026
c3352f6
Add the akit tool page and the subscription principle behind it
ftschindler Oct 14, 2026
76d910d
Record the version-stamp principle the migration notices came from
ftschindler Oct 14, 2026
4367b73
Bring the fkb tool page and the federation decision up to what shipped
ftschindler Oct 14, 2026
aa7dd30
Write out the retired federation layer, which had stood as a stub
ftschindler Oct 14, 2026
535bbf0
Add two findings and a guide from the Precision 5470 boot-failure inv…
ftschindler Oct 14, 2026
8d027a2
Survey what an MCP server can be, and the principle it leaves behind
ftschindler Oct 14, 2026
241452e
Give the link checker a browser that does not depend on a cache
ftschindler Oct 14, 2026
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
10 changes: 9 additions & 1 deletion .github/workflows/governance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,15 @@ jobs:
- name: Cache prek environments
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/prek
# Both directories, because the linkspector hook's environment is split
# across them. `prek` holds the node_modules; puppeteer's browser lands
# in its own cache from a postinstall script. Restoring only the first
# gives a hook environment that looks complete and has no browser: the
# install step never re-runs, so nothing fetches Chrome. They are cached
# under one key so a restore cannot bring back half the pair.
path: |
~/.cache/prek
~/.cache/puppeteer
key: prek|${{ steps.setup-python.outputs.python-version }}|${{ hashFiles('.pre-commit-config.yaml') }}
restore-keys: |
prek|${{ steps.setup-python.outputs.python-version }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/merge-back.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ permissions:
jobs:
merge_back:
name: 🔁 (merge main back into staging)
uses: ftschindler/federated-knowledge-skills/.github/workflows/merge-back.yml@a56c34298a91ded8f88519a027d38ff930ea9ae1
uses: ftschindler/federated-knowledge-skills/.github/workflows/merge-back.yml@5b0771e12f55d1ce5f598f4e42e788bcac4c4447 # v0.2.2
with:
branch: staging
permissions:
Expand Down
21 changes: 16 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,22 @@ repos:
hooks:
- id: linkspector
name: Check broken links (linkspector)
# The chrome version here ...
entry: bash -c 'npx --yes puppeteer browsers install chrome@148.0.7778.97 && linkspector check -c .linkspector.yml'
# `browsers install chrome` names no version on purpose: the pinned puppeteer
# below resolves its own, so this cannot drift from the pin the way a literal
# `chrome@...` does. It is a cheap no-op once the browser is in the cache, and
# it is what makes the check independent of whether anything downloaded a
# browser earlier - a restored hook environment, or an npm that declines to
# run puppeteer's postinstall, otherwise leaves it with no browser at all.
entry: bash -c 'puppeteer browsers install chrome && linkspector check -c .linkspector.yml'
language: node
# ... needs to match the linkspector version here.
additional_dependencies: ['@umbrelladocs/linkspector@0.5.3', 'puppeteer']
# puppeteer is pinned exactly, not left to linkspector's own `^24.37.5`.
# It is what decides which Chrome launches: each release downloads and runs
# the revision compiled into it, so a floating range here means the browser
# resolving a link changes without anything in this repository changing.
# 24.39.1 -> Chrome 146.0.7680.76. How to choose the pair, and how to read
# the mapping out of a release:
# https://raw.githubusercontent.com/ftschindler/knowledge/staging/docs/guides/pin_the_browser_behind_a_pre_commit_link_checker.md
additional_dependencies: ['@umbrelladocs/linkspector@0.5.3', 'puppeteer@24.39.1']
pass_filenames: false
files: \.md$
# -- Python formatting + linting --
Expand Down Expand Up @@ -130,7 +141,7 @@ repos:
# use here; pinning it by revision is what keeps this bundle standalone without
# carrying a copy that could drift.
- repo: https://github.com/ftschindler/federated-knowledge-skills
rev: a56c34298a91ded8f88519a027d38ff930ea9ae1 # frozen: v0.2.1
rev: 5b0771e12f55d1ce5f598f4e42e788bcac4c4447 # frozen: v0.2.2
hooks:
- id: okf-concepts
args: [--bundle-root, docs, --floor, docs/fkb.yaml]
Expand Down
1 change: 1 addition & 0 deletions docs/blueprints/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
The concrete, copyable artefact a decision produced.

- [MkDocs Material PKB publishing stack](mkdocs_material_pkb_publishing_stack.md) - the stack this site runs on, component by component, with the reasons attached
- [Sharing one knowledge bundle on two branches](sharing_one_knowledge_bundle_on_two_branches.md) - what a bundle looks like once several people file into it, and the published branch takes no direct pushes
104 changes: 104 additions & 0 deletions docs/blueprints/sharing_one_knowledge_bundle_on_two_branches.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
---
type: Blueprint
title: Sharing one knowledge bundle on two branches
description: The branch shape, manifest field, workflow and command that let several people file
into one knowledge bundle whose published branch takes no direct pushes.
tags:
- blueprint
- okf
- federation
- knowledge-management
- ci-cd
- collaboration
status: stable
generated:
by: opencode/claude-opus-5
at: '2026-10-14T09:40:00Z'
---
The shape a knowledge bundle takes once more than one person files into it, produced by
[Federating my knowledge base as privacy-tiered OKF bundles](../decisions/federating_my_knowledge_base_as_privacy_tiered_okf_bundles.md)
and shipped in
[federated-knowledge-skills](../tools/federated_knowledge_skills_schindler.md) v0.2.0. The
bundle you are reading runs it, and so does one shared with an employer's team.

The constraint it answers: a bundle anyone publishes from has a default branch that takes no
direct pushes, so a change reaches it through a pull request and a review. That gate is
measured in days. The knowledge it gates is written in minutes.

## Manifest - what is assembled

**Two branches, each with one job.**

| | the default branch | the shared branch |
| --- | --- | --- |
| Holds | what has been reviewed, and what the site is built from | everything reviewed, plus what is not ready |
| Protected | yes: pull request and review | no: open to anyone with write access |
| Who works here | nobody directly | every checkout, every agent |
| Reaches the other by | a release pull request, on a cadence | a merge, after every change to the default |

- **Merge commits only**, on both routes. Squash and rebase merging write a new commit with
the same content and no shared ancestry, so the two branches stop knowing they ever met.
The next release then re-displays everything already released.
- **One manifest field**, `sync: staging`, naming the branch this machine's checkout belongs
on. It says three things at once: check this branch out, commits here may leave the
machine, and somebody else is writing here so read it fresh.
- **A reusable merge-back workflow**, called by the bundle on every push to the default
branch and pinned by full commit SHA:

```yaml
on:
push:
branches: [main]

jobs:
merge_back:
uses: ftschindler/federated-knowledge-skills/.github/workflows/merge-back.yml@<sha>
with:
branch: staging
permissions:
contents: write
pull-requests: write
```

- **The governance checks run on the shared branch too**, not only on pull requests. A page
edited in the forge's web editor has no working copy, so the bundle's own pre-commit hooks
never ran on it, and the shared branch is where such a change lands first.

## Principles it instantiates

[Pin GitHub Actions to full commit SHAs](../principles/pin_github_actions_to_full_commit_shas.md),
which matters more here than usual: this workflow holds write access to the shared branch.
[Grant least-privilege CI permissions at both workflow and job level](../principles/grant_least_privilege_ci_permissions_at_both_workflow_and_job_level.md),
[Guard invariants at commit-time, not review-time](../principles/guard_invariants_at_commit_time_not_review_time.md)
and [Mirror every local guard in CI](../principles/mirror_every_local_guard_in_ci.md), which is
what the web-editor case above is. The branch shape itself is
[Give a shared repository two clocks when review is slower than the work](../principles/give_a_shared_repository_two_clocks_when_review_is_slower_than_the_work.md).

One principle this departs from on purpose:
[Keep a linear history](../principles/keep_a_linear_history_block_merge_fixup_and_squash_commits.md)
cannot hold where two long-lived branches have to keep meeting. Shared ancestry is the thing
being protected, and a merge commit is what records it.

## Operating manual

- **Before reading the bundle**, `fkb sync <bundle>`. This is what stops an answer coming from
a page a teammate corrected this morning.
- **After filing**, commit through the bundle's hooks, then `fkb sync <bundle>` again. Pushing
is part of filing here in the way committing is.
- **The command refuses rather than resolves.** It does not stash, switch branches or finish a
rebase. Uncommitted work in the tree, a checkout on another branch, a conflict: the commit
stands, nothing is pushed, and the reason is one line of output. An unpushed commit costs a
teammate an hour's delay; a guess about whose work was in the tree costs the work.
- **Cutting a release is `gh pr create`** from the shared branch to the default one, on
whatever cadence suits. Nothing automates it, because a command that did would put a forge
inside the CLI.

## What it costs

**A concept is shared with the team before it is reachable by URL.** It sits on the shared
branch, where every checkout sees it, whilst the site still carries the last release. A
cross-bundle citation written today therefore points at a page the world cannot open yet, and
nothing detects this: the link checker asks whether the file exists on this disk, and it does.

**A conflict stops the machine every time.** Two people writing the same concept is a question
about what is true, which is the last thing to hand to an agent working quickly.
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,14 @@ The bundle you are reading, as the `public` tier: an OKF bundle over the same
MkDocs stack, with the hooks doing what the engine used to.

The federation layer around it is
[federated-knowledge-skills](https://github.com/ftschindler/federated-knowledge-skills),
currently at design stage. Its blueprint is not written here yet, deliberately,
for the same reason the previous decision deferred one: it should be documented
from a working setup rather than guessed at. This decision points forward to it.
[federated-knowledge-skills](../tools/federated_knowledge_skills_schindler.md), which has been
released and in daily use since September. There are now four bundles at three tiers: this
public one, a sealed private one, one shared with an employer's team, and a read-only upstream.

The blueprint this decision deferred is written, now that there is a working setup to write it
from rather than a design to guess at:
[Sharing one knowledge bundle on two branches](../blueprints/sharing_one_knowledge_bundle_on_two_branches.md)
is the shape a bundle takes once more than one person files into it.

## What I gave up, and would reconsider

Expand Down
3 changes: 2 additions & 1 deletion docs/explorations/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ date, the work simply ended. The argument for keeping them as their own genre is
[Record an abandoned exploration as an exploration, not a superseded decision](../knowledge_management/record_an_abandoned_exploration_as_an_exploration_not_a_superseded_decision.md).

- [Running this knowledge base on awiki](running_this_knowledge_base_on_awiki.md) - my trial of agent-first authoring on a dedicated wiki engine, the four things that ended it, and the much longer list of what survived
- [Wrapping the kb skills in a federation layer](wrapping_the_kb_skills_in_a_federation_layer.md) - a stub: the federation layer that wrapped another skill set, reached a green test suite, and was retired
- [Wrapping the kb skills in a federation layer](wrapping_the_kb_skills_in_a_federation_layer.md) - the federation layer that wrapped another skill set, reached a green test suite, and was retired for making prose the control flow
- [Tracking AI authorship with git-ai](tracking_ai_authorship_with_git_ai.md) - three and a half months of recording which lines an agent wrote, never reading any of it, and what that says about collecting data because collecting is cheap
95 changes: 95 additions & 0 deletions docs/explorations/tracking_ai_authorship_with_git_ai.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
---
type: Exploration
title: Tracking AI authorship with git-ai
description: Three and a half months of recording which lines an agent wrote, ended because the data
was never read and the notes it wrote cluttered every history view that shows all refs.
tags:
- exploration
- git-ai
- git
- provenance
- agents
status: stable
generated:
by: opencode/claude-opus-5
at: '2026-10-09T14:00:00+02:00'
sources:
- id: felix-git-ai-opencode
resource: https://ftschindler.github.io/running-linux/done/20260624-git-ai-integration-with-opencode/
title: Git AI Integration with OpenCode
author: human:felix_schindler
last_modified: '2026-06-24'
---
[git-ai](../tools/git_ai.md) was installed on 24 June 2026 and removed on 9 October. In between
it did exactly what it promised, without once being asked for anything.

## What it was for

Most of the code in these repositories is written by an agent, and the question of which lines
those are has no answer in git itself. `git blame` names whoever ran the commit. A tool that
records the agent, the model and the prompt behind each line promises to turn that into
something answerable, and `git ai blame` renders it beside the ordinary blame.

The setup was done carefully and written up at the time[^felix-git-ai-opencode]: telemetry off,
version checks off, auto-updates off, so the tool ran entirely locally. `prompt_storage` was set
to `notes` deliberately, so attribution and prompts travelled with the repository rather than
sitting in a database on one machine. Each repository was configured to fetch and push
`refs/notes/ai` alongside its branches.

## What actually happened

Nothing, which is the finding.

The collection worked. Three and a half months later, 125 repositories held a `.git/ai`
directory and 19 carried notes. All of it was recorded passively, pushed with the code, and
**never read once**. Not a single `git ai blame`, `git ai stats` or `git ai log` was run against
a real question.

That is not a failure of the tool, and it is worth separating from one. The attribution data was
accurate and present the whole time. What was missing was a question to put to it.

## Why it stopped

**The value is in the analysis, and the analysis is the commercial product.** What the open
source CLI gives one person is a per-file, per-commit view, answered one command at a time. The
things the data would actually be interesting for - how much agent-written code survives review,
which models produce work that needs reworking, what a change really cost - are aggregations
across many commits and many repositories, and those live in the Teams product. Collecting the
data locally without that is keeping records for an audit nobody is going to run.

**The notes clutter every history view that shows all refs.** `refs/notes/ai` is a ref like any
other, so anything drawing `--all` draws it, and the note commits carry no subject line:

```text
15ee53d (HEAD -> redesign-as-skill) design the redesign: this config as an installable skill
abd9bf4
1171be4 (origin/main, main) add a getting-started guide for fresh machines
1414586
69660c8 update my AGENTS.md
```

Two of those five rows are notes. In `gitk`, which is where a branch view gets read rather than
grepped, they interleave with the real history as blank entries and have to be looked past every
time. A background cost, paid at every glance, against a benefit taken zero times.

## What it cost to leave

More than it cost to adopt, which is the asymmetry worth recording. The installer wrote into
four unrelated places and the removal had to reach all four, including per-repository state in
125 repositories and notes already pushed to remotes. The procedure is
[Uninstall git-ai and the state it left in every repository](../guides/uninstall_git_ai.md), and
it is longer than the install instructions by a wide margin.

**A tool that needs no per-repository setup leaves per-repository state anyway.** That is the
general shape, and it is not specific to this tool: the property that makes something frictionless
to adopt is usually the same property that spreads it somewhere nothing is tracking.

## What would bring it back

A question worth asking of the data. If the aggregate view ever matters here - measuring what
agent-written code costs over its life rather than at the moment it is written - then this is the
tool that already has the standard, the agent coverage and the git-native storage to answer it.
Nothing about the implementation was the problem.

Until then the honest position is that the data was collected because collecting it was easy, not
because anyone intended to use it.
Loading
Loading