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
197 changes: 106 additions & 91 deletions .abide/rubric.json
Original file line number Diff line number Diff line change
@@ -1,191 +1,206 @@
{
"version": 1,
"compiledAt": "2026-09-26T00:00:00.000Z",
"compiledAt": "2026-10-06T12:30:00.000Z",
"compiledBy": "claude",
"sources": [
{
"path": "CLAUDE.md",
"sha": "555a20d66f1b750123f05622c861eca3704c4246e29622ff32a81a3abbd34027",
"scope": "**/*"
"scope": "**/*",
"sha": "afb77c5e8ffc502330baa870089e75bc042b19b79dca1bd839176051baf1076d"
}
],
"rules": [
{
"id": "run-both-test-packages",
"text": "Testing (ALWAYS RUN BOTH)",
"id": "node-test-runner-only",
"text": "Tests use `node:test`.",
"source": {
"path": "CLAUDE.md",
"line": 16
"line": 14
},
"scope": [
"**/*.ts",
"**/*.mts",
"**/package.json"
],
"check": {
"type": "unenforceable",
"reason": "about which commands the agent runs, not the code"
"type": "lint",
"how": "no-restricted-imports for vitest, jest, @jest/*, mocha; grep package.json for those dependencies",
"pattern": "(from\\s+['\"](vitest|jest|@jest/[\\w-]+|mocha)['\"]|\"(vitest|jest|mocha)\"\\s*:)"
},
"status": "active"
},
{
"id": "no-unified-remark",
"text": "Custom line-by-line state machine for parsing markdown (no unified/remark)",
"id": "zshy-bundler-only",
"text": "The build uses `zshy`, not tsc.",
"source": {
"path": "CLAUDE.md",
"line": 54
"line": 14
},
"scope": [
"packages/mdcode/**/*.ts",
"packages/mdcode/package.json"
"**/package.json"
],
"check": {
"type": "lint",
"how": "no-restricted-imports for unified, remark, remark-* and mdast packages; grep package.json dependencies",
"pattern": "(from\\s+['\"](unified|remark[\\w-]*|mdast[\\w-]*)['\"]|\"(unified|remark[\\w-]*)\"\\s*:)"
"how": "grep package.json build scripts for a tsc emit build or rollup",
"pattern": "\"(build|dev)\"\\s*:\\s*\"[^\"]*\\b(tsc(?![^\"]*--noEmit)|rollup)\\b"
},
"status": "active"
},
{
"id": "use-define-transform",
"text": "Use `defineTransform()` helper for type safety.",
"id": "ts-extension-imports",
"text": "Relative imports keep their `.ts` extension: `import { parse } from \"./parser.ts\"`.",
"source": {
"path": "CLAUDE.md",
"line": 92
"line": 15
},
"scope": [
"packages/usage/**/*.ts",
"packages/example/**/*.ts",
"packages/mdcode/src/**/*.test.ts",
"**/*transform*.ts",
"**/*transform*.mts"
"**/*.ts",
"**/*.mts"
],
"when": "edit",
"check": {
"type": "model",
"question": {
"type": "boolean",
"instructions": "Among the added lines, is there `export default`, `const transformer =` or `transformer:` followed directly by an arrow function or `function` taking `{ tag, meta, code }`, with no `defineTransform(` around it? Answer false if no transformer is added.",
"criteria": {
"true": "export default ({ tag, code }) => tag === 'sql' ? code.toUpperCase() : code;",
"false": "export default defineTransform(({tag, code}) => {\n if (tag === 'sql') return code.toUpperCase();\n return code;\n});"
}
}
},
"status": "active",
"calibration": {
"at": "2026-09-26T15:12:04.977Z",
"hunks": 3,
"median": 0.8683,
"min": 0.8596,
"max": 0.9016,
"fired": 3,
"verdict": "skipped"
}
"type": "lint",
"how": "node/file-extension-in-import (set to always in shared/.eslintrc.json)",
"pattern": "from\\s+['\"]\\.{1,2}/[^'\"]*(?<!\\.ts)(?<!\\.json)['\"]",
"overlaps": "node/file-extension-in-import"
},
"status": "active"
},
{
"id": "zshy-bundler-only",
"text": "zshy - TypeScript bundler (not tsc, not rollup)",
"id": "run-cli-from-source",
"text": "Run the CLI from source with `node --experimental-strip-types packages/mdcode/src/main.ts list README.md`.",
"source": {
"path": "CLAUDE.md",
"line": 111
"line": 16
},
"scope": [
"**/package.json",
"**/rollup.config.*"
],
"check": {
"type": "lint",
"how": "grep package.json build scripts and dependencies for rollup or a tsc emit build; path check for rollup.config.*",
"pattern": "\"(build|dev)\"\\s*:\\s*\"[^\"]*\\b(tsc(?![^\"]*--noEmit)|rollup)\\b|\"rollup\"\\s*:"
"type": "unenforceable",
"reason": "about how the agent runs the CLI, not the code"
},
"status": "active"
},
{
"id": "node-test-runner-only",
"text": "node:test - Native test runner (NOT vitest, jest, or mocha)",
"id": "no-unified-remark",
"text": "Parser (`src/parser.ts`): a custom line-by-line state machine instead of remark, so in-place updates keep exact character offsets.",
"source": {
"path": "CLAUDE.md",
"line": 112
"line": 20
},
"scope": [
"**/*.ts",
"**/*.mts",
"**/package.json"
"packages/mdcode/**/*.ts",
"packages/mdcode/package.json"
],
"check": {
"type": "lint",
"how": "no-restricted-imports for vitest, jest, @jest/*, mocha; grep package.json for those dependencies",
"pattern": "(from\\s+['\"](vitest|jest|@jest/[\\w-]+|mocha)['\"]|\"(vitest|jest|mocha)\"\\s*:)"
"how": "no-restricted-imports for unified, remark, remark-* and mdast packages; grep package.json dependencies",
"pattern": "(from\\s+['\"](unified|remark[\\w-]*|mdast[\\w-]*)['\"]|\"(unified|remark[\\w-]*)\"\\s*:)"
},
"status": "active"
},
{
"id": "tsconfig-strict-verbatim",
"text": "TypeScript: strict mode, `verbatimModuleSyntax: true`, ES2022 target",
"id": "shared-scan-fences",
"text": "`scanFences()` is shared by `parse()` and `updateInfoStrings()` so block indices always agree.",
"source": {
"path": "CLAUDE.md",
"line": 115
"line": 20
},
"scope": [
"**/tsconfig*.json"
],
"check": {
"type": "deferred",
"reason": "asserts options are present, which needs the resolved config including extends, not a diff"
"reason": "needs both callers in parser.ts, not one hunk"
},
"status": "active"
},
{
"id": "ts-extension-imports",
"text": "File Imports Must Use .ts Extension",
"id": "extract-plans-before-writing",
"text": "`extract()` runs `planExtract()` before writing anything.",
"source": {
"path": "CLAUDE.md",
"line": 21
},
"check": {
"type": "deferred",
"reason": "needs extract()'s whole control flow"
},
"status": "active"
},
{
"id": "update-reads-through-read-source",
"text": "`update()` reads every `file=` through `readSource()`.",
"source": {
"path": "CLAUDE.md",
"line": 21
},
"check": {
"type": "deferred",
"reason": "needs update()'s whole control flow"
},
"status": "active"
},
{
"id": "update-never-writes",
"text": "`update()` never writes; the CLI decides, and only `--apply` writes the markdown.",
"source": {
"path": "CLAUDE.md",
"line": 131
"line": 22
},
"scope": [
"**/*.ts",
"**/*.mts"
"packages/mdcode/src/commands/update.ts"
],
"check": {
"type": "lint",
"how": "node/file-extension-in-import (already set to always in shared/.eslintrc.json)",
"pattern": "from\\s+['\"]\\.{1,2}/[^'\"]*(?<!\\.ts)(?<!\\.json)['\"]",
"overlaps": "node/file-extension-in-import"
"how": "no-restricted-imports of node:fs write APIs and ../write.ts in update.ts",
"pattern": "\\b(writeFile|writeAtomic|appendFile|createWriteStream)\\s*\\("
},
"status": "active"
},
{
"id": "before-commit-tests",
"text": "Before Committing: `pnpm test` - Ensure ALL 138 tests pass",
"id": "watch-writes-only-with-apply",
"text": "`watch()` writes only with `apply`.",
"source": {
"path": "CLAUDE.md",
"line": 142
"line": 22
},
"check": {
"type": "unenforceable",
"reason": "about running commands before a commit, not the code"
"type": "deferred",
"reason": "needs watch()'s control flow around each write"
},
"status": "active"
},
{
"id": "before-commit-build",
"text": "Before Committing: `pnpm build` - Ensure build succeeds",
"id": "config-only-with-flags",
"text": "`mdcode.config.json` is loaded only with `--project` or `--config`. Without either, input defaults to stdin.",
"source": {
"path": "CLAUDE.md",
"line": 143
"line": 23
},
"check": {
"type": "unenforceable",
"reason": "about running commands before a commit, not the code"
"type": "deferred",
"reason": "needs every config load site in cli.ts"
},
"status": "active"
},
{
"id": "contract-source-of-truth",
"text": "`COMMAND_NAMES` and `ERROR_CODES` are the source of truth for commands and error codes.",
"source": {
"path": "CLAUDE.md",
"line": 24
},
"check": {
"type": "deferred",
"reason": "needs the existing lists in result.ts; docs.test.ts checks the READMEs against them"
},
"status": "active"
},
{
"id": "before-commit-typecheck",
"text": "Before Committing: `pnpm -r lint:ts` - Type check all packages",
"id": "read-command-doc-first",
"text": "Adding or changing a command, flag or error code: `docs/agents/adding-a-command.md`",
"source": {
"path": "CLAUDE.md",
"line": 144
"line": 28
},
"check": {
"type": "unenforceable",
"reason": "about running commands before a commit, not the code"
"reason": "about what the agent reads, not the code"
},
"status": "active"
}
Expand Down
2 changes: 1 addition & 1 deletion .bumpy/library-cli-boundary.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
mdcode-ts: minor
---

Removed `transform()` and `transformWithFunction()`, which printed every block to the console and `transform()` read stdin; use `update({ source, transformer, filter })` instead. Added `update({ onBlock })` to report each block as it finishes, and `extract()` now throws an `invalid_usage` error for `updateSource` with `ignoreAnonymous`.
Removed `transform()` and `transformWithFunction()`, which printed every block to the console and `transform()` read stdin. `update({ source, transformer, filter })` replaces `transformWithFunction()` but reads each `file=` first, so a missing file is a `read_failed` error (thrown, or collected with `continueOnError`); use `walk()` to transform without reading files. Added `update({ onBlock })` to report each block as it finishes, and `extract()` now throws an `invalid_usage` error for `updateSource` with `ignoreAnonymous`.
5 changes: 3 additions & 2 deletions TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ Two packages, `packages/mdcode` (published as `mdcode-ts`) and `packages/usage`.
test file with `node --test` skips that, so run `pnpm build` first.
- Region fixtures live in `packages/mdcode/tests/testdata/region/` and are compared byte-for-byte, so
trailing newlines matter.
- Tests that assert on warnings use `mock.method(console, "error", …)` with `mock.restoreAll()` in a
`finally`, so a failing assertion cannot leak the stub into sibling tests.
- Library code prints nothing, so assert on returned results and callbacks (`onBlock`, `onEvent`),
not on console output. ESLint rejects `console`, process stdio and exit in `packages/mdcode/src`
except `cli.ts`, `main.ts` and `*.test.ts`.

## Before Pushing

Expand Down
1 change: 1 addition & 0 deletions docs/agents/issue-tracker.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Issues and specs for this repo live as GitHub issues. Use the `gh` CLI for all o
- **Comment on an issue**: `gh issue comment <number> --body "..."`
- **Apply / remove labels**: `gh issue edit <number> --add-label "..."` / `--remove-label "..."`
- **Close**: `gh issue close <number> --comment "..."`
- **After closing an issue**: list the open issues it blocked with `gh api 'repos/{owner}/{repo}/issues/<n>/dependencies/blocking' --jq '.[] | select(.state=="open") | .number'`, plus any open issue whose body says `Blocked by` it (`gh search issues --repo <owner>/<repo> --state open '"#<n>" in:body'`). Check each one's Agent Brief against `main` and comment with whatever the close made outdated: criteria now met, a stale "Verification status", names that have changed.

Infer the repo from `git remote -v` — `gh` does this automatically when run inside a clone.

Expand Down
36 changes: 35 additions & 1 deletion packages/mdcode/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,39 @@
"node": {
"version": ">=20.*"
}
}
},
// Library code returns results; only the CLI adapter (cli.ts, main.ts) prints, reads stdin or exits.
"rules": {
"no-console": "error",
"no-restricted-properties": [
"error",
{ "object": "process", "property": "stdin", "message": "Library code takes its input as an argument; only cli.ts reads stdin." },
{ "object": "process", "property": "stdout", "message": "Library code returns results; only cli.ts and main.ts write output." },
{ "object": "process", "property": "stderr", "message": "Library code returns results; only cli.ts and main.ts write output." },
{ "object": "process", "property": "exit", "message": "Only main.ts ends the process, through exitCode." },
{ "object": "process", "property": "exitCode", "message": "Only main.ts sets the exit code." }
],
"no-restricted-imports": [
"error",
{
"paths": [
{
"name": "node:process",
"importNames": [ "stdin", "stdout", "stderr", "exit", "exitCode" ],
"message": "Library code returns results; only cli.ts and main.ts touch process I/O."
}
]
}
]
},
"overrides": [
{
"files": [ "src/cli.ts", "src/main.ts", "src/**/*.test.ts" ],
"rules": {
"no-console": "off",
"no-restricted-properties": "off",
"no-restricted-imports": "off"
}
}
]
}
2 changes: 1 addition & 1 deletion packages/mdcode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"bp": "pnpm version prerelease --no-git-tag-version && pnpm --filter mdcode-ts build && pnpm pack",
"build": "zshy",
"dev": "zshy --watch",
"test": "node --test-reporter=spec --test './{tests,src}/**/*.test.ts'",
"test": "node --test-reporter=dot --test './{tests,src}/**/*.test.ts'",
"test:watch": "node --watch --test-reporter=spec --test './{tests,src}/**/*.test.ts'",
"prepublishOnly": "pnpm build",
"lint:ts": "tsc --noEmit",
Expand Down
2 changes: 1 addition & 1 deletion packages/usage/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"private": true,
"scripts": {
"pretest": "pnpm --filter mdcode-ts build",
"test": "node --test-reporter=spec --test tests/**/*.test.ts",
"test": "node --test-reporter=dot --test tests/**/*.test.ts",
"test:watch": "pnpm test -w",
"lint:ts": "tsc --noEmit",
"lint": "tsc --noEmit && eslint \"{tests,fixtures,examples}/**/*.{j,t}s{,x}\" --cache --max-warnings=0",
Expand Down
Loading