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
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
---
id: T-0147
title: Truncating a context bundle overwrites the flag saying it was truncated
status: backlog
status: done
type: bug
priority: low
area: mcp
tags: [mcp, truncation]
created: 2026-08-03
updated: 2026-08-04
updated: 2026-08-07
origin: [T-0146]
scope: [packages/workfile/src/modules/mcp]
verified:
at: "2026-08-07T22:58:23.966Z"
method: local
commit: 9cfb0175194fc944ab34f527c800adf4c1b486d2
digest: "sha256:cae81812f469d88981a5082dbb9a514cb0a7a5dea3d6cd3a5dc15cdea30befb5"
---

`buildAgentContext` returns `truncated: boolean` — true when related records
Expand Down Expand Up @@ -43,9 +49,18 @@ not an assumption to build a fix on.

## Acceptance criteria

- [ ] A test drives `project_agent_context` past `maxToolResultBytes` and pins
- [x] A test drives `project_agent_context` past `maxToolResultBytes` and pins
what the two markers do to each other
- [ ] The byte-ceiling marker stops colliding with tool payload fields — a
- [x] The byte-ceiling marker stops colliding with tool payload fields — a
distinct key, or a nested one
- [ ] `outputSchema` for `project_agent_context` matches whatever wins
- [ ] `docs/mcp.md` describes the marker that ends up shipping
- [x] `outputSchema` for `project_agent_context` matches whatever wins
- [x] `docs/mcp.md` describes the marker that ends up shipping

## Activity

- 2026-08-07 22:17Z illodev@local#42eb42f5 · claimed
- 2026-08-07 22:58Z illodev@local#42eb42f5 · released

## Notes

- 2026-08-07 22:58Z illodev@local#42eb42f5 — local verification: Reproduced first, as the card asked: with the ceiling lowered, project_agent_context returned `truncated: {"records":1}` where its outputSchema declares a boolean. The byte-ceiling marker is `resultTruncated` now, declared in every tool's schema rather than merely allowed, and docs/mcp.md says which of the two is the transport speaking. Mutation-proven: restoring the shared key reports the object where a boolean belongs. Full gate green at 484 + 10 tests.
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
---
id: T-0160
title: --root accepts any directory and answers from an empty workspace
status: backlog
status: done
type: bug
priority: low
area: core
effort: S
scope: [packages/workfile/bin/workfile.ts, packages/workfile/src/core]
scope: [packages/workfile/bin/workfile.ts, packages/workfile/src/workspace]
origin: [T-0159]
created: 2026-08-04
updated: 2026-08-04
updated: 2026-08-07
verified:
at: "2026-08-07T22:58:21.710Z"
method: local
commit: 9cfb0175194fc944ab34f527c800adf4c1b486d2
digest: "sha256:e3afefb220be80685c23d420ec7ffb7e24b0f57d9ada1868fbf6dd3707a8c919"
---

`workfile doctor --root packages/workfile` runs. It reports six
Expand Down Expand Up @@ -48,8 +53,17 @@ resolving to a parent would be a second surprise rather than a fix.

## Acceptance criteria

- [ ] `--root` pointed at a directory with no workspace marker is an error
- [ ] The error names the directory and points at `init` or `--allow-new`
- [ ] `--allow-new` still accepts a not-yet-workspace directory
- [ ] Nothing writes a cache into a directory that failed the check
- [ ] `pnpm run check` green, doctor 0/0
- [x] `--root` pointed at a directory with no workspace marker is an error
- [x] The error names the directory and points at `init` or `--allow-new`
- [x] `--allow-new` still accepts a not-yet-workspace directory
- [x] Nothing writes a cache into a directory that failed the check
- [x] `pnpm run check` green, doctor 0/0

## Activity

- 2026-08-07 22:17Z illodev@local#42eb42f5 · claimed
- 2026-08-07 22:58Z illodev@local#42eb42f5 · released

## Notes

- 2026-08-07 22:58Z illodev@local#42eb42f5 — local verification: The card's own reproduction now exits 2: `doctor --root packages/workfile` names the directory and both ways forward instead of reporting six issues and exiting 0. `card list` the same. `--allow-new` still accepts one, and it reaches that branch now — it never did, which is how an explicit root came to check nothing. A real workspace is unaffected, and nothing was written into the directory that failed. Strict rather than a walk, so a root inside a workspace is refused rather than silently resolved upward; both halves mutation-proven.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
id: T-0218
title: A doctor issue never says which module produced it
status: backlog
status: done
type: task
priority: low
area: core
Expand All @@ -11,6 +11,11 @@ scope: [packages/workfile/src/modules/health]
origin: [T-0213, LRN-0029]
created: 2026-08-07
updated: 2026-08-07
verified:
at: "2026-08-07T22:58:22.248Z"
method: local
commit: 9cfb0175194fc944ab34f527c800adf4c1b486d2
digest: "sha256:61c9511a6a827f6742debb2f977250551ca11a5c12bfa39a84e008eb55a0a76d"
---

Found while auditing T-0213.
Expand All @@ -37,6 +42,20 @@ field, leave `code` alone, and have the CLI and the Health view group by it.

## Acceptance criteria

- [ ] A doctor issue carries the module that produced it, integrations included.
- [ ] An existing accepted baseline still matches, proven by a test.
- [ ] The CLI and `/api/v2/health` consumers can tell an integration's finding from a core one.
- [x] A doctor issue carries the module that produced it, integrations included.
- [x] An existing accepted baseline still matches, proven by a test.
- [x] The CLI and `/api/v2/health` consumers can tell an integration's finding from a core one.

## Activity

- 2026-08-07 22:17Z illodev@local#42eb42f5 · claimed
- 2026-08-07 22:58Z illodev@local#42eb42f5 · released

## Notes

- 2026-08-07 22:26Z illodev@local#42eb42f5 — The card's premise was wrong and it changed where the fix goes. It says "every module that reports health returns `{ module, issues }`" — only the integration registry does. `diagnoseCards`, the docs, changelog and memory reports, `checkAgentInstructions` and `checkCiTemplates` all return `{ counts, ok, issues }` and no module at all, so stamping `report.module` onto each issue produced `module: undefined` on 1396 of the 1397 findings in a real workspace. Verified by running it against one rather than by reading.

So the module is named where `runDoctor` calls each reporter, which is the only place that knows which one it is calling — one table, and the reports hanging off the shared index are tagged rather than mutated so the routes that also serve them are untouched.

The other decision the card left open is settled the way it guessed: `module` is a field and `code` is untouched. Namespacing the code would read better and would change `issueIdentity`, which is what a baseline is matched by, so every baseline accepted with `--accept-baseline` would go stale at once for a cosmetic gain. Pinned by a test that fails if the identity grows the field.
- 2026-08-07 22:58Z illodev@local#42eb42f5 — local verification: Against Fube's 1397 findings: every one now names its module, 0 with none, where the first attempt produced `undefined` on 1396 because the card's premise about `{ module, issues }` was wrong. The CLI's summary groups by module and code, so `integration:<id>/<code>` is unmistakable. An accepted baseline still matches, pinned by a test that fails if `issueIdentity` grows the field — mutation-proven.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
id: T-0219
title: The claim board carries no session, so the guard misses a shared actor
status: backlog
status: done
type: task
priority: low
area: core
Expand All @@ -12,6 +12,11 @@ related: [T-0089]
origin: [T-0206, LRN-0030]
created: 2026-08-07
updated: 2026-08-07
verified:
at: "2026-08-07T22:58:23.322Z"
method: local
commit: 9cfb0175194fc944ab34f527c800adf4c1b486d2
digest: "sha256:89f0faee49e0513ddaf53fd252b98b26d1965433665d6a7341bf822cd8ab4cdf"
---

The residual ADR-0020 left open, recorded in full in LRN-0030.
Expand Down Expand Up @@ -40,7 +45,16 @@ T-0089 is already about board staleness and would pass through the same code.

## Acceptance criteria

- [ ] A board entry carries the session resolved for its claim, from either source.
- [ ] Both writers of the board — `rebuildClaimBoard` and the hook's `buildBoard` — produce the same entry for the same card, proven by a test.
- [ ] Two agents sharing an explicit `--actor` make the guard prompt, proven by driving the real hook.
- [ ] The hook's latency budget still holds.
- [x] A board entry carries the session resolved for its claim, from either source.
- [x] Both writers of the board — `rebuildClaimBoard` and the hook's `buildBoard` — produce the same entry for the same card, proven by a test.
- [x] Two agents sharing an explicit `--actor` make the guard prompt, proven by driving the real hook.
- [x] The hook's latency budget still holds.

## Activity

- 2026-08-07 22:17Z illodev@local#42eb42f5 · claimed
- 2026-08-07 22:58Z illodev@local#42eb42f5 · released

## Notes

- 2026-08-07 22:58Z illodev@local#42eb42f5 — local verification: Driven through the real hook, four cases: the holding session stays silent; two agents with different sessions and the same explicit --actor now prompt, which is the case LRN-0030 left open; two session-free terminals with one actor stay silent because unproven is not a verdict; different actors prompt. The board carries the session from either source and both writers agree, pinned by a test that now compares the rule the guard applies rather than assuming null on both sides — it was agreeing by coincidence. The latency budget test passes, and the hot path gained one string operation and no I/O. Two mutations caught: dropping the field, and collapsing the guard back to comparing actors.
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
---
id: T-0223
title: Only cards notice that a retitled record's filename went stale
status: backlog
status: done
type: bug
priority: low
area: core
tags: [health]
effort: S
scope: [packages/workfile/src/modules/health]
scope: [packages/workfile/src/modules/health, packages/workfile/src/modules/memory, packages/workfile/src/modules/docs, packages/workfile/src/modules/changelog]
origin: [LRN-0033]
created: 2026-08-07
updated: 2026-08-07
verified:
at: "2026-08-07T22:58:22.788Z"
method: local
commit: 9cfb0175194fc944ab34f527c800adf4c1b486d2
digest: "sha256:5efda4a8354950bd3d335c443a3dc01b1322f0ea7a1ba1395fd32aacc719fd49"
---

`diagnoseCards` reports `filename-stale` when a card's filename no longer matches its title, and `doctor --fix` renames it. The comment there states the reason plainly: the filename is the handle people and agents grep by, and a stale one misdirects long after anyone remembers the rename.
Expand All @@ -21,8 +26,17 @@ Worth doing as one pass rather than per collection, and worth doing at all for t

## Acceptance criteria

- [ ] `doctor` reports a stale filename for a memory record, a managed document and a changelog fragment, as a warning, the way it does for a card.
- [ ] `doctor --fix` renames them, and a record whose id is referenced elsewhere keeps resolving.
- [ ] The rule is written once rather than per collection.
- [ ] A record whose filename is stale because it was renamed *by hand* to something legitimate is not fought over — state what happens.
- [ ] `pnpm run check` green, doctor 0/0.
- [x] `doctor` reports a stale filename for a memory record, a managed document and a changelog fragment, as a warning, the way it does for a card.
- [x] `doctor --fix` renames them, and a record whose id is referenced elsewhere keeps resolving.
- [x] The rule is written once rather than per collection.
- [x] A record whose filename is stale because it was renamed *by hand* to something legitimate is not fought over — state what happens.
- [x] `pnpm run check` green, doctor 0/0.

## Activity

- 2026-08-07 22:17Z illodev@local#42eb42f5 · claimed
- 2026-08-07 22:58Z illodev@local#42eb42f5 · released

## Notes

- 2026-08-07 22:58Z illodev@local#42eb42f5 — local verification: One record of each kind created, retitled and repaired end to end with the shipped binary: four `filename-stale` warnings attributed to cards, memory, docs and changelog, then `doctor --fix` renamed all four, ids still resolve and doctor is quiet. On Fube the rule finds 53 findings that were invisible — 35 docs, 18 memory. The exclusions were checked rather than assumed: an indexed README is not compared, and a released fragment cannot even be retitled — the protocol answers CHANGE_FRAGMENT_RELEASED — so that exclusion covers a hand edit. The rule caught my own hand-rename of LRN-0033 one commit after I made it, which is what closed criterion 5.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
id: T-0224
title: A trailing-separator strip is written five times, as a regex
status: backlog
type: chore
priority: low
area: core
tags: [security]
effort: S
created: 2026-08-07
updated: 2026-08-07
---

CodeQL reported `js/polynomial-redos` against `routeRoots` in `docs/validation.ts`: `replace(/\/+$/, "")` retries the anchored `+` from every start position, so a value of N slashes costs O(N²). The alert was high and it blocked a pull request that had nothing to do with it.

The spelling appeared five times across the package and once more in the hook, and only one of them was reachable from a declared value — which is why only one was reported. Replaced by `stripTrailingSlashes` in `core/glob.ts`, a linear loop, plus the hook's own copy since it imports nothing.

Filed to record what is not done: nothing stops the sixth copy. The suite has no rule against an anchored quantifier over a value that comes out of config or a card, and CodeQL only reports the ones whose taint it can follow.

## Acceptance criteria

- [ ] A test or lint rule fails on an anchored `+`/`*` applied to a config or record value.
- [ ] It names the linear alternative rather than only refusing.
- [ ] The existing six sites pass it.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
id: CHG-0155
title: An explicit --root is checked the way a discovered one always was
type: fixed
area: core
visibility: public
cards: [T-0160]
created: 2026-08-07
updated: 2026-08-07
---

`--root` pointed at a directory with no workspace marker is an error naming the directory, instead of a clean, empty, believable answer from somewhere that is not a workspace. `--allow-new` is the way through and reaches that branch now; it only ever reached the path that already refused one.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
id: CHG-0156
title: Every doctor finding says which module produced it
type: changed
area: core
visibility: public
cards: [T-0218, T-0223]
created: 2026-08-07
updated: 2026-08-07
---

A diagnostic returned by a repository's own healthCheck used to read exactly like one Workfile made itself. Each finding now carries its reporter and the CLI groups by it, so `integration:<id>/<code>` is unmistakable. Accepted baselines are unaffected: the identity a baseline matches on deliberately ignores the field. The stale-filename rule moved to the layer that holds every kind while this was open, so memory records, managed documents and unreleased changelog fragments are checked and repaired too — they derived their filenames from their titles all along and only cards ever noticed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
id: CHG-0157
title: The scope guard sees two agents that share an actor
type: fixed
area: core
visibility: public
cards: [T-0219]
created: 2026-08-07
updated: 2026-08-07
---

A claim board entry carries the session behind its claim, so the guard no longer has to recover one from the actor's tail — which a `claimed_by` written from an explicit `--actor` does not have. Two agents handed the same actor saw a string equal to their own and the guard stayed silent; it prompts now. The session that holds a claim is still free to work, and two session-free terminals sharing one actor are still left alone, because unproven is not a verdict.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
id: CHG-0158
title: The MCP byte ceiling stops overwriting a tool's own truncation flag
type: fixed
area: mcp
visibility: public
cards: [T-0147]
created: 2026-08-07
updated: 2026-08-07
---

A reply degraded for exceeding maxToolResultBytes is marked `resultTruncated`, not `truncated`. The old key collided with what project_agent_context already meant by it: a large bundle replaced a boolean with an object, so a caller checking `=== true` survived only because an object is truthy, and a caller reading `truncated.records` on any other tool got `true` from that one. Both markers are declared in every tool's outputSchema.
29 changes: 24 additions & 5 deletions packages/workfile/bin/workfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import {
renderChangelog,
renumberCard,
reslugStaleCardFiles,
reslugStaleRecordFiles,
reopenCard,
runUpgrade,
runDoctor,
Expand Down Expand Up @@ -2633,7 +2634,15 @@ async function main() {
// still wrote. `ensureWritable` is the guard that actually holds.
const workspace = await loadWorkspace(
explicitRoot
? { root: explicitRoot, readOnly: has("--read-only") }
? {
root: explicitRoot,
// `--allow-new` reaches both branches now. It did not, which
// is how `--root` came to check nothing at all: the flag that
// means "accept a directory that is not yet a workspace" was
// only wired to the path that already refused one (T-0160).
allowMissing: has("--allow-new"),
readOnly: has("--read-only")
}
: {
cwd: root,
allowMissing: has("--allow-new"),
Expand All @@ -2656,10 +2665,10 @@ async function main() {
let fixed:
| (Awaited<ReturnType<typeof healDuplicateRecordIds>> & {
renamed: Awaited<
ReturnType<typeof reslugStaleCardFiles>
ReturnType<typeof reslugStaleRecordFiles>
>["moves"];
renameSkipped: Awaited<
ReturnType<typeof reslugStaleCardFiles>
ReturnType<typeof reslugStaleRecordFiles>
>["skipped"];
trails: Awaited<
ReturnType<typeof healMisplacedTrailEntries>
Expand All @@ -2672,7 +2681,11 @@ async function main() {
// Renaming runs after the ID repair: a card that just moved to a
// fresh ID keeps the old title slug, and this is what brings the
// whole filename back in step.
const renamed = await reslugStaleCardFiles(workspace, { actor });
// Every kind, not only cards. Memory records, managed documents and
// unreleased changelog fragments derive their filenames from their
// titles the same way and had neither the rule nor the repair
// (T-0223).
const renamed = await reslugStaleRecordFiles(workspace, { actor });
// Last, because both repairs above rewrite whole files and this one
// reads the body it finds afterwards.
const trails = await healMisplacedTrailEntries(workspace, { actor });
Expand Down Expand Up @@ -2786,9 +2799,15 @@ async function main() {
}
// Grouped counts, so a wall of one repeated rule reads as one
// problem rather than as hundreds.
// Keyed by module and code, not by code alone. A core `code` implies
// its module to anyone who knows the codebase; a diagnostic returned
// by a repository's own `healthCheck` implies nothing, and used to
// read exactly like one Workfile produced. `integration:<id>/<code>`
// is unmistakable (T-0218).
const byCode = new Map();
for (const issue of shown) {
byCode.set(issue.code, (byCode.get(issue.code) || 0) + 1);
const key = issue.module ? `${issue.module}/${issue.code}` : issue.code;
byCode.set(key, (byCode.get(key) || 0) + 1);
}
if (byCode.size) {
console.log("\nBy rule:");
Expand Down
Loading
Loading