Commit graph

39 commits

Author SHA1 Message Date
4496c19045 feat(m2): add sak skill with case templates 2026-09-05 21:55:37 +02:00
52fd5c26a7 docs(m2): correct where the O4 gate lands and why
The first draft pinned the gate to Step 21. Wrong distinction: the gate lives
in the launcher, so it covers tools reached through the MCP server and not a
skill that calls a script directly, which is how Steps 21 and 22 write. Step 21
is where the defect starts; it is not where the gate has to land. Pinned to
Step 24 instead, with the one-file fence amendment named.

Co-Authored-By: Claude <claude-opus-5>
2026-09-05 21:43:54 +02:00
651d996004 docs(m2): decide O4 - the launcher refuses to serve without the ingestion guard
The order that carried plan steps 17-20 required this decision to be taken in
M2 rather than allowed to slide. Taken: the gate moves onto the guard, the
version floor stays as the second gate, selvsjekk keeps reporting.

Deciding argument: every skill degrades to manual paste without jobbsok-tools,
so failing closed costs a connector that does not appear, while failing open
costs untrusted text persisted through the one boundary this plugin exists to
hold.

Scoped deliberately: M2's first write (--oppdater) persists four derived
values and no untrusted byte, so it is not the trigger. Step 21 and Step 22
are, and the implementation is pinned there because it lives in files outside
this order's fence.

Co-Authored-By: Claude <claude-opus-5>
2026-09-05 21:41:41 +02:00
12125c1932 feat(m2): add deterministic sak_status cli with golden output
--workspace is required with no environment fall-back: this process runs
unsandboxed, and a status run that quietly picked a workspace is one nobody
can audit. --check exits 1 on a stale cache, distinct from 2 for a failed run.

Byte-stability is measured, not asserted: the CLI is run twice under two
PYTHONHASHSEED values and the bytes compared, in both output formats. The
write boundary gets two digest tests rather than one -- without --oppdater
every byte stays put, with it exactly the diverging case's four cached keys
move and logg.jsonl, the body and every other file are byte-identical.

Co-Authored-By: Claude <claude-opus-5>
2026-09-05 21:40:32 +02:00
b80bd00781 test(m2): build the eight-case fixture workspace
Eight cases, one per build-brief 6 state, dated relative to 2026-09-15. One
of them (havbris) carries frontmatter that deliberately disagrees with its
log, so the derived-truth rule has a fixture instead of only a docstring. One
(vaeroy-sjomat) is named for an employer with ae, o-slash and a-ring, so the
ASCII path rule has teeth on macOS NFD.

Six beslutninger.jsonl fixtures for M6's counting rules are written now, in
M2, because the store is append-only: type, skjema, delscore and vekt_hash
have to be right at the first write or they are wrong forever.

Co-Authored-By: Claude <claude-opus-5>
2026-09-05 21:38:24 +02:00
8d577dc3ca feat(m2): add silence flags at the 14, 7 and 10 day thresholds
Iron Law deviation, stated rather than hidden: these tests passed on the
first run. The silence computation landed in 4669cc3 because neste_frist is
one of Step 17's four cached keys and is derived from the same thresholds --
the plan entangles the two steps, so the code could not be written in the
order the steps are numbered. What this file adds is the boundary evidence:
13/14/15, 6/7/8, 9/10/11, an autoreply that must not move the clock, and the
H9 distinction between no inbound RECORDED and no inbound existing.

Co-Authored-By: Claude <claude-opus-5>
2026-09-05 21:35:11 +02:00
4669cc363d feat(m2): implement event enum and status transition table
Status derives from logg.jsonl alone; sak.md frontmatter is a cache and the
log wins where they disagree (risk H3). --oppdater is the only writer of the
four cached keys, and it leaves a key alone when the derivation is None --
neste_frist in vurderer/soker is the operator's application deadline and no
refresh may eat it.

Two rows of the plan's table are not 5.3 events. Decision ja and operator
close reach the case log as the decision-log records build-brief 5.7 already
mirrors there, so the event enum stays closed at eleven.

Co-Authored-By: Claude <claude-opus-5>
2026-09-05 21:34:04 +02:00
dcd3eae534 feat(port): move five bash entry points to Python and drop bash from .mcp.json
jobbsok could not start on stock Windows. `.mcp.json` named `bash` as the
command, and the five entry points behind it reached for grep, sed, find, awk,
zip and unzip. `bootstrap.sh` built the virtualenv, so a Windows adopter could
not even reach an interpreter. Two adopters are waiting and neither is
guaranteed to be on macOS, so this is the install, not a rough edge.

The Python layer was already clean -- no /tmp, no /usr, no os.uname, no home
directory assumption -- so only the shell layer moved. Behaviour is carried
over unchanged; the deliberate exceptions are listed in docs/.

THE ONE OPEN DECISION, AND WHY IT WAS FORCED

How does .mcp.json start an interpreter without a POSIX shell, when it is
called python3 on macOS and python or py on Windows? Measured against the
installed CLI, not assumed:

  - The plugin mcpServers stdio schema has NO platform-conditional form. A
    config carrying invented windows/darwin/platform keys was accepted and the
    keys were silently discarded -- it fails quietly, not loudly.
  - ${VAR:-default} IS expanded, in command, args and env.
  - ${VAR} without a default is not safe: unset, it is passed through
    unexpanded, so the spawn would try to run a program named ${VAR}.
  - Windows spawns with shell:false, so a .py path as command is out.
  - No single literal works. On this Mac, python and py are not on PATH.

So the default form is the only lever the schema offers:
"${JOBBSOK_LAUNCH_PYTHON:-python3}". macOS and Linux keep working with nothing
set; Windows sets one variable and needs no shell.

A SECOND VARIABLE, NOT A REUSE OF JOBBSOK_PYTHON

JOBBSOK_PYTHON names the interpreter to SERVE on: the launcher treats it as an
explicit operator choice, so it wins over the bootstrapped virtualenv. A
Windows adopter setting it merely to spell `python` would silently bypass that
virtualenv and serve WITHOUT the ingestion guard. JOBBSOK_LAUNCH_PYTHON only
says how to start the launcher. A test asserts the two never collapse into one.

O4 IS LEFT STANDING

The launcher still gates on the interpreter's version rather than on the guard
being importable. That is the shell version's semantics carried over on
purpose: harmless while nothing writes, a defect from M2, and an M2 decision.

VERIFY

  - pytest tests/                      -> 124 passed, exit 0 (was 112)
  - grep -c '"command": "bash"' .mcp.json -> 0
  - git ls-files 'scripts/*.sh'        -> 0
  - README install block names Windows, and neither WSL nor Git Bash
  - server started end to end exactly as .mcp.json expands, and answered
    initialize and tools/list
  - the ported probe checker reproduces the shell version's output and exits 0

NOT MEASURED, AND NOT ASSUMED

Nothing here has ever run on Windows. Whether Cowork on Windows bridges to a
host-side stdio MCP as it does on this Mac is unmeasured -- docs/cowork-probe.md
covered macOS only. docs/cross-platform-port.md says what a Windows probe would
have to measure, and records two findings left deliberately untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 21:02:03 +02:00
f8c74fa0ac docs(m1): record Cowork verification outcome 2026-09-05 20:33:34 +02:00
c2ee7b6788 docs(m1): correct the install block for external adopters
The install block was written under the assumption that this project has no
users but its author. It has two, so every claim in it is now load-bearing.

Three corrections, each measured rather than reasoned:

- The guard's host is reachable. `git.fromaitochitta.com` resolves to a public
  address, serves a valid certificate and answers `git ls-remote` with no
  credentials, no global git config and no terminal prompt; a non-existent
  repository on the same host fails, so the query can distinguish. A full
  anonymous `pip install` of the pinned `v1.3.0` builds a wheel and imports at
  1.3.0. The old text told a third party they probably could not reach it.
- The Claude Code route did not work. `claude plugin install` resolves only
  through a marketplace and `jobbsok` is not among the catalog's twelve
  plugins, so the documented command failed at its first step. It is marked as
  landing with milestone 2 instead of being printed as if it worked.
- Windows is named as unsupported, with the specific reason: the entry points
  are shell scripts and `.mcp.json` starts the tool server through `bash`. The
  Python underneath is already platform-clean, so the gap is packaging.

Prerequisites are stated up front, with a preflight command that needs nothing
installed, because an adopter should learn about a blocked network before
building a virtualenv rather than after.

Outside Step 16's Files list, and so a separate commit rather than part of the
pinned Cowork-verification commit, following the precedent of 90ef620.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 20:11:13 +02:00
8a3320bce9 chore(m1): green suite gate before milestone two 2026-09-05 06:58:00 +02:00
90ef620cfc fix(m1): build the public-surface canary at runtime so the scanner scans itself 2026-09-05 06:57:16 +02:00
a9153e043e docs(m1): add security model and complete the readme first screen 2026-09-05 06:56:23 +02:00
0e3295f52b test(m1): assert plugin manifest and pin a build stamp 2026-09-05 06:52:40 +02:00
89bcd1b038 feat(m1): add jobbsok-tools stdio mcp server and launcher 2026-09-05 06:48:55 +02:00
28a950bcea fix(m1): replace transcribed operator values in fixtures with invented ones 2026-09-04 21:35:24 +02:00
cdb4a8fda9 feat(m1): add kandidatvurdering skill that persists nothing 2026-09-04 21:30:14 +02:00
3c74968ec1 feat(m1): compute scores from sub-scores and weights in a script 2026-09-04 21:27:38 +02:00
f75718d82c feat(m1): add kandidatprofil skill with schema reference 2026-09-04 21:22:31 +02:00
ac7de63dc1 feat(m1): validate kandidat.md against the section 5.1 contract 2026-09-04 21:18:33 +02:00
b538b5c5f0 test(m1): seed anonymised fixture corpus with hygiene scan 2026-09-04 19:36:06 +02:00
d0be8fba11 test(m1): add conftest fixtures and golden helpers 2026-09-04 19:30:58 +02:00
aa7fe4b553 feat(m1): add append-only jsonl writer with folding reader 2026-09-04 19:28:28 +02:00
a0874a5e17 feat(m1): add strict frontmatter parser with documented grammar 2026-09-04 19:26:18 +02:00
8328d487a9 docs(m1): narrow the host-dependency risk to the case that actually bites
The note said a plugin MCP tool needs this Mac "awake, connected and carrying
the right Python", and called sleep a state that would occur routinely. Two
things were wrong with that.

This operator's Mac does not sleep, so the named instance does not apply. And
there is no wider population to generalise to: build-brief lines 3 and 22 scope
the whole system to a single operator searching for their own job, and jobbsok
is not in the marketplace catalog. A worry about "most other users" is a worry
about people this project does not have.

The dependency itself is real; only the instance was wrong. The one that
survives a never-sleeping Mac is Cowork opened from somewhere else: the bridge
is named remote-devices and the server is a child of Claude.app on THIS Mac, so
a session started on a phone or a second laptop may or may not reach it. That
was not measured, and the note now says so in both directions rather than
guessing.

Stated precisely rather than darkly, because an overstated risk gets discounted
and takes the real one with it. The degradation branch now has two named
triggers instead of one vague one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 19:07:20 +02:00
8f0c2fa360 docs(m1): answer the Cowork probe - Step 1 Verify now exits 0
All four bullets answered by the operator in a live Cowork session. The step's
Verify goes from exit 1 to exit 0, which is the whole point of having written
it as a `test` rather than a count-and-eyeball: it was red for a reason and is
now green for a reason.

  - Sesjonsmodus: sky
  - Host-MCP: ja
  - python3: ja 3.11.15
  - CLAUDE_PLUGIN_ROOT: ja

Host-MCP is `ja` on both halves of the rule, not just the first. The connector
was listed AND `probe_ping` returned `probe-tools 0.0.1, python 3.14.0 @
/usr/local/bin/python3` -- a macOS path and the host interpreter, against the
sandbox's 3.11.15 [GCC 13.3.0]. The call crossed from the cloud session to this
Mac. That was the prediction stated before the call, so the test could have
falsified it.

Consequence recorded alongside the capability, because it is a cost: a
plugin-declared MCP tool in Cowork needs this machine awake, connected and
carrying the right Python. A cloud session elsewhere reaches nothing. The
degradation branch is therefore also the answer to "this Mac is asleep" -- a
routine state, not an exceptional one.

Run 2 of the two-run record was dropped rather than quietly skipped. It was
designed on the expectation that the off run would fail; it did not, and the
default configuration already gives a working host-side server. Confirming that
local mode also works has low value against its cost, so it is deferred with
the reason stated and named as the first thing to try if the cloud path breaks.

One negative result worth keeping: an individual tools/call does NOT reach
main.log. `grep -c probe_ping main.log` returned 0 both before and after a
successful call. The check script already refused to read that absence as an
answer; this records why that caution was correct.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 19:04:56 +02:00
41854ae01a docs(m1): record three probe findings outside the four gated bullets
Recorded here rather than as extra bullets: the four are gated on a count of
exactly four and Step 31 adds a fifth, so a sixth would break the Verify.
Checked both ways -- the live file still counts 0, a fully answered copy still
counts exactly 4.

O1. Host-side MCP reaches a cloud session through localMcpBridge. research/03
    dimension 4 quoted Anthropic saying local MCP servers do not run in cloud
    sessions, and flagged as unverified whether they still spawn on this Mac.
    They do: main.log records Connecting / negotiated 2025-11-25 / Connected
    (1 tools) / announcing, and ps shows the server as a child of Claude.app on
    the host Python 3.14. The session addresses it as
    mcp__remote-devices__plugin_jobbsok-probe_probe-tools__probe_ping. So
    "host-side server" and "cloud session" are not exclusive here.

O2. Sandbox and host are two different interpreters: 3.11.15 [GCC 13.3.0] Linux
    inside, 3.14.0 macOS outside, with CLAUDE_PLUGIN_ROOT resolving under
    /root/.claude/plugins/synced/. That is also what answers the session-mode
    question -- measured from the paths, not read off a settings screen. It
    matters for M3: guard_ingest.py cannot import the guard in the sandbox, so
    it belongs on the host side of the bridge that O1 shows is reachable.

O3. Cowork's built-in browser rendered a Finn job ad in full, with the operator
    logged in after signing in by hand inside that browser. That is the brief's
    invariant by a shorter route -- no credential handling, manual auth, an
    already-authenticated browser. It raises an M3 question about what
    Playwright over CDP still adds; the question is recorded, not decided.
    Two limits are stated rather than glossed: login persistence across
    sessions was not measured, and the built-in browser leaves no host-side
    trace, so unlike O1 and O2 the operator's observation is the only
    instrument.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 19:02:06 +02:00
a4e6b44df4 fix(m1): correct two wrong queries in the Cowork probe check
Both were caught by running the check against a state whose answer was already
known from the screen, which is the only reason they were caught at all. A
check that reports PENDING for a thing that has plainly happened is worse than
no check: it invites the reader to distrust the instrument or, worse, to
believe it.

1. It looked for a directory named after the plugin. Cowork installs into
   rpm/plugin_<opaque id>/, so the name appears in no path -- only in
   rpm/manifest.json, which is now what gets read. The old code also used an
   unquoted $(find ...) in a for-loop over a path containing "Application
   Support", so the space split every path in half. That is the exact
   word-splitting failure this machine's shell rules warn about, and it made
   the check report "not installed" while the server was running.

2. It expected ~/Library/Logs/Claude/mcp-server-<name>.log. That naming is for
   Claude Desktop's own connectors; a plugin's MCP server is logged by
   LocalMcpServerManager into main.log. An absent file at a guessed path is a
   statement about the guess, not about the world.

What the corrected check now reads, and what it establishes:

  rpm/manifest.json  -> jobbsok-probe, plugin_01NxHfqM495jtJzjVjRvgWDm,
                        marketplace "My Uploads", installed 2026-09-04T16:48:20Z
  installed copy     -> 4 files, manifest version 0.0.1 (no stale cache)
  main.log 18:48:28  -> Connecting to plugin:jobbsok-probe:probe-tools
                        negotiated protocol version: 2025-11-25
                        Connected ... (1 tools)
  process            -> pid 12997, /usr/local/bin/python3, a child of Claude.app

So a plugin-declared stdio MCP server does spawn and connect on this Intel Mac,
on the host interpreter rather than in the sandbox. The negotiated version also
vindicates one design choice: probe_tools.py echoes the client's protocolVersion
instead of asserting its own default, and the client asked for 2025-11-25, which
is newer than the default the server would otherwise have claimed.

Still open, and deliberately not inferred from the above: whether a tools/call
to probe_ping actually returns. main.log records the connection lifecycle, not
individual calls, so that one is confirmed in the chat or not at all -- the
script says so rather than treating a missing line as an answer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 18:51:45 +02:00
e1ad1b688f test(m1): add a host-side progress check for the Cowork probe
The probe's four questions are answered by the operator in Cowork, but three of
them leave traces on this Mac, and a trace is a measurement where "what did you
see in the UI" is a recollection. This reads the traces so each step can be
verified before the next one starts.

What it can actually see, discovered by measurement rather than assumed:

- Claude Desktop writes one log per MCP server, named mcp-server-<name>.log,
  and it records the full JSON-RPC handshake (verified against the existing
  mcp-server-filesystem.log: initialize, notifications/initialized, tools/list
  all appear as "Message from client: method=..."). So if Cowork spawns
  probe-tools, the entire exchange is readable from disk -- including the
  server's own stderr line, which carries the interpreter version.
- The absence of that log file is itself informative: it means Cowork never
  attempted to start the server, which is a different finding from starting
  and failing.
- pgrep on probe_tools.py confirms a live process, but its absence is not a
  nei: a server may be spawned on demand. The log is the record; the process
  check is corroboration. The script says so rather than over-claiming.

The Cowork store search is bounded to the session directory by name plus the
small manifests. The first version ran a recursive content grep over the whole
Claude support directory and did not finish inside two minutes -- it walks
caches and VM images. A check that hangs is not a check. Bounded version runs
in 0.17s.

It reports, it does not gate: exit 0 when everything has landed, 1 while
anything is pending. Baseline now: vehicle and archive done, three pending.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 18:43:22 +02:00
c56820ea01 docs(m1): record the probe twice, once per session-location setting
Cowork exposes session location as a toggle ("Only on this computer"), which
the probe's four bullets cannot express: each holds one value, and the file was
written as if session mode were a property of the machine to be discovered
rather than a setting that can be flipped.

So the probe is now run twice, off then on, into a two-run table in the Results
section, with a second table saying how to read the pair. The three outcomes are
all useful and lead somewhere different -- the toggle is the lever (and then it
belongs in README and in every Verifiser i Cowork step, not a footnote); host
MCP is dead whatever the setting (degradation branch, and jobbsok-tools is not
built on an assumption); or host MCP runs even in cloud mode, which contradicts
the documentation and is exactly what research/03 dimension 4 flagged as
unverified.

Off is run first because it is the current default and a fresh install returns
to it -- one click away from being lost for free.

Section A now says to probe both sides of the setting rather than just read it,
and carries an honesty marker: that this toggle controls session location is an
inference from its label and subtitle, not something any documentation read so
far names by that string. The probe settles it.

The four bullets, the allowed answers and the Verify command are untouched.
Checked rather than assumed: on a fully answered copy the verify regex still
matches exactly 4 lines and exits 0 -- for the `lokal VM`/`ja` filling and for
the `sky`/`nei` one -- so the added tables cannot become false positives. The
live file still exits 1, as it must while the probe is open.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 18:36:33 +02:00
96af9526c1 docs(m1): add a step-by-step walkthrough for running the Cowork probe
Addendum to plan Step 1, which stays open: the four bullets are still
`ubesvart` and the step's Verify still exits 1. Nothing measured changed.

The previous instruction was one sentence -- "upload via Customize -> Plugins,
run probe-versjon, check whether probe-tools appears" -- which assumed the
reader already knew where each of those lives and what counts as an answer.
The walkthrough now lives in the file the answers get written into, rather than
in a chat message that scrolls away.

Every UI path and caveat is sourced from research/03, not invented: the
install-from-file and drag-into-chat routes, connectors appearing under the
plugin's connectors page rather than as skills, the stale-cache issue (#69020)
that makes the 0.0.1 version check load-bearing, and the upload-does-not-
survive-relaunch report (#65426).

Two distinctions are called out because getting them wrong would produce a
confident wrong answer:

- `- python3:` measures the SANDBOX interpreter, not the host's. Cowork runs
  shell commands in the VM or in the cloud. It is still the right thing to
  measure, but it says nothing about the virtualenv Step 2 built.
- `- Host-MCP:` does run on the host, since local MCP servers were moved out
  of the sandbox in May 2026. A `ja` there is what decision 7 rests on, so it
  requires both that the connector is listed and that probe_ping answers --
  "present but mute" is recorded as nei with a note, because absent and mute
  point at different causes.

Section A is ordered first on purpose: cloud sessions are documented not to run
local MCP servers, so `sky` plus a `ja` on Host-MCP would contradict the
documentation and is a more interesting result than either answer alone.

The verify command in section F was executed as written from the rendered
document, not just pasted in: exit 1, as it must be while the probe is open.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 17:39:09 +02:00
1d631b7c94 feat(m1): add pinned workspace root and ASCII slug helpers
Plan Step 3. Verify green: `.venv/bin/python -m pytest tests/test_paths.py` ->
8 passed, exit 0. Full suite: 11 passed.

Test first, and RED was observed before a line of paths.py existed
(ModuleNotFoundError on collection).

Two measured risks drive the design, not one.

Risk C6 -- the host-side server runs unsandboxed with caller-supplied paths.
safe_join resolves BOTH operands with os.path.realpath before comparing, so
lexical traversal and symlink escape fall to the same check; a lexical check on
the joined string would have called the symlink case safe. The comparison is
component-aware, so a sibling whose name merely prefixes the root is outside.
Escape raises; it never clamps to an adjacent path.

Risk H8 -- macOS hands back NFD. slug normalises to NFC first, so a decomposed
name and its composed twin produce one slug rather than two directories. The
test asserts that equality on a real NFC/NFD pair, not on a lookalike.

No implicit default workspace: --workspace, else JOBBSOK_WORKSPACE, else a
`workspace:` line in ${CLAUDE_PLUGIN_DATA}/jobbsok.conf, else WorkspaceUnresolved.
Guessing at the operator's home directory is the one behaviour this module must
not have. This tightens build-brief section 5's `~/jobbsok-workspace` default
into an explicit order; the deviation is recorded in the plan's Assumptions.

scaffold is idempotent by only creating what is absent -- an existing
beslutninger.jsonl is never truncated. It is append-only, and a scaffold that
emptied it would destroy the decision log. The test proves this by populating
the workspace and asserting a byte-identical snapshot across a second run.

Collision rule is documented and tested: -2, then -3, counting up.

sak_id carries no `taken` parameter. An earlier draft had one; no step and no
test requires it, so it was unproven code and was removed rather than kept.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 17:29:52 +02:00
24768ef66b build(m1): pin toolchain and guard v1.3.0 in pyproject
Plan Step 2. Verify green: `bash scripts/bootstrap.sh && .venv/bin/python -m
pytest tests/test_toolchain.py` -> 3 passed, exit 0.

Test first, and the RED was demonstrated rather than assumed: run against a
venv carrying pytest and the guard but not pytest-socket, the third test fails
with ModuleNotFoundError while the other two pass. The test discriminates.

pyproject.toml declares the dependencies as a PEP 735 group rather than an
installable distribution -- nothing here needs a build backend, since the tests
import from scripts/ via pytest's pythonpath. Measured before choosing it: pip
26.2.1 on this machine supports `install --group`, and bootstrap upgrades pip
first, so a pip too old fails loudly instead of silently skipping the guard.

The guard is pinned to the exact tag v1.3.0 and is not vendored. Verified the
tag resolves and the package exposes __version__ == "1.3.0"; that equality is
what the test asserts, not the pip metadata.

addopts carries --disable-socket and --strict-markers, which is why the
`network` marker is declared: with strict markers an undeclared marker would
make `-m 'not network'` an error rather than a filter, and the offline
guarantee would quietly stop being tested.

bootstrap.sh builds under ${CLAUDE_PLUGIN_DATA} when set (survives a plugin
update) and falls back to the repo-root .venv for development -- risk H2. It
refuses an interpreter below 3.10 instead of building on it, because python3
here can resolve to 3.9.6 under a GUI-spawned process.

Exercised, not just written: re-running is idempotent (exit 0); --med-xlsx
installs the extra (openpyxl 3.1.5 importable); an unknown argument exits 2
with usage; a missing JOBBSOK_PYTHON interpreter refuses loudly. bash 3.2-clean
(system /bin/bash 3.2.57 -n passes) and ASCII-only.

.gitignore needed no change: `.venv/` was already on line 12 from the initial
commit, and `git check-ignore -v .venv` confirms it matches.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 17:26:20 +02:00
0f92b2a1c3 docs(m1): record Cowork probe results and degradation branch
Plan Step 1. Builds the probe vehicle and the checklist; does NOT answer it.

The four questions all require the operator in a live Cowork session on this
Mac, and that is a fact about the measurement rather than a caveat: measured
2026-09-04, the session mode is not readable from disk (cowork_settings.json
carries only extraKnownMarketplaces; the desktop config carries
coworkNetworkMode and a trusted-folder list, neither states where a session
runs), and the other three each need a plugin installed in a live session.

The step's Verify is therefore RED on purpose and stays red until the operator
answers:
  test "$(grep -cE '...' docs/cowork-probe.md)" = 4   -> exit 1 (0 of 4 matched)
Validated in both directions: a fully answered copy exits 0, a copy with three
of four answered exits 1. The gate can pass, so its failure means something.

Probe vehicle: jobbsok-probe, exactly four files under
tests/fixtures/cowork-probe/ -- manifest, skills/probe-versjon/SKILL.md,
.mcp.json declaring the stdio server probe-tools, and probe_tools.py (standard
library only). Smoke-tested from Claude Code: initialize handshake completes,
tools/list returns probe_ping, tools/call returns an interpreter version, an
unknown tool name is rejected. What is NOT verified is whether Cowork ever
starts it -- that is the question.

Archive is built from an explicit four-file include list, never zip -r over the
repo root; verified to contain no .git, STATE.md, .venv or .claude/ entry.

The degradation branch for each possible nei is written down now, before any
answer is known, so it is not decided under pressure later.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 17:22:59 +02:00
2f99ac5951 test(plan-gates): gate-scriptene fra revisjonen 04.09, kjoerbare fra repoet
De to maskinelle sjekkene paa at planrevisjonen holder laa i en scratchpad og
var i ferd med aa bli borte (samme naeromkomst som oekt 2s script). Flyttet inn
i repoet, gjort sted-uavhengige (repo-roten resolveres fra $0) og med en guard
naar plan.md/brief.md mangler -- de er local-only paa denne offentlige remoten,
og «fant ingenting» skal ikke lese som «alt groent» (exit 2, ikke 0).

Kjoert fra tests/plan-gates/ etter flyttingen:
  brief_criteria.sh  PASS=33 FAIL=0
  stale_text.sh      PASS=12 FAIL=0

Negativkontroll: mot en tom plan gir brief_criteria.sh PASS=6 FAIL=27 og exit 1,
og manglende fil gir SKIP + exit 2. Gaten kan altsaa feile.

PM-autorisert forsteg (amendment til ordre 20260904T150903Z), utenfor planens
sesjon-1-gjerde.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 17:18:06 +02:00
2076f617e0 docs(plan): revisjon etter kvalitetsrevisjonen 04.09
Operatoerbeslutning (b): revider planen foer M1. Ordre
20260904T135502Z-687411640-from-.claude, ti punkter, alle utfoert i
plan.md og brief.md under .claude/projects/ (local-only, gitignored) --
denne seksjonen i revisjonsrapporten er den sporede baereren.

Gater ved levering, kjoert 2026-09-04:
- plan-validator.mjs --strict: valid, 56 steg, 0 errors/warnings
- brief-validator.mjs: valid, 0 errors/warnings
- oekt 2s gate-script (33 grep-kriterier): PASS=33 FAIL=0
- grep -c 'Pass 2' plan.md: 6
- de tolv "Foreldet tekst"-greppene: PASS=12 FAIL=0

Ingen kode skrevet, ingen M1-steg utfoert. 56 steg inn, 56 steg ut.

Tre avvik fra ordreteksten, begrunnet i rapporten: --check i 5 av 6
Cowork-steg (steg 16 er M1, sak_status.py finnes foerst i steg 17);
det tolvte scriptet i avviksnoten er jobbsok_tools.py, ikke
kandidat_schema.py (som allerede sto der); Pass 2-seksjonen staar sist
i fila, der trekplan.md anviser, med peker fra planhodet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 17:07:11 +02:00
775e291642 docs: kvalitetsrevisjon av planleggingen (maal, ikke bygg)
Measures the two planning sessions and plan.md against docs/build-brief.md:
step traceability (0 of 56 unanchored, 15 derived), blocker closure (5 of 9
closed in step text, 4 partial), review-count discrepancies, dedup false
negative reproduced (Jaccard 0.236/0.404 vs 0.7), session token and cost
profile, and whether /trekplan --brief docs/build-brief.md is a valid input
(it is not: FM_MISSING). Recommendation: revise the listed steps first, then
M1 GO. No change to plan.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-04 08:06:55 +02:00
91b078f694 chore: gitignore .claude/projects (local-only on public remote); brief validated
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 21:17:47 +02:00
05a7aa1d6f feat: initial commit — plugin manifest and build brief
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 20:27:23 +02:00