Repository navigation
fix(cli): os dev --no-watch turns watch off, and a PACKAGE matching nothing fails loudly - #20839
Conversation
…hing nothing fails Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB Co-authored-by: Claude <noreply@anthropic.com>
…ch PACKAGE Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 25 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c47a0af007f0097dd0a7508be81b527c2a00052c && git checkout c47a0af007f0097dd0a7508be81b527c2a00052c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b28054654d12e6faf92509a69fa416d9f5b56c82 c36db55077d3f439811ec609f19bbbc9f7dae30c && git checkout -B drift-repro b28054654d12e6faf92509a69fa416d9f5b56c82 && git merge --no-ff c36db55077d3f439811ec609f19bbbc9f7dae30c
node scripts/docs-audit/affected-docs.mjs --json b28054654d12e6faf92509a69fa416d9f5b56c82 |
Contract reviewServed-tier: Inputs read: card #20681 body and its three comments (triage 5895852973, claim 5908367350, os-dev-report 5909733295); PR #20839 body, file list (3 files, +257/-4, 261 changed lines, head repo is the base repo, no governed path) and the net diff from merge-base ① Derived judgments(a)
(b)
(c)
(d) Exported
(e) Changeset
Gates and pin. All seven required contexts ② Semver levelDecision: keep By AGENTS.md's changeset section the
③ Boundary flagsOpen question (bare Deviations
Out-of-scope findings
Reviewer's own flags Implemented-by: VERDICT: PASS Generated by Claude Code |
…ackages/observability/src and packages/verify/src to the commits that decided them (objectstack-ai#20842) Part of objectstack-ai#20594 Clause-②: no ## What changed This is stage 13 of the `domain:cli` lane of the dead-citation sweep: `packages/observability/src` and `packages/verify/src` in **one PR**. The maintainer ruled that in the handover `5903477632` on the card (「合成一个 PR (Recommended)」). The ruling changes the one-package-per-PR direction for these two packages only, so each package's before and after census counts are listed separately below. Every comment site in these two trees that cited a tracker number answering 404 now cites the commit in this repository's history that made the decision the line describes. The form is ruling C+D's form C (comment `5749154545` on objectstack-ai#19123), as stages 1 to 12 of this card applied it. The last stage was `plugin-dev`, landed as `f7c6d65f5`. The card stays open for its later stages, so this PR says `Part of`. In total, **4 sites on 4 lines in 3 files, covering 3 numbers**, now cite **3 distinct commits**: - `observability`: **1 site**, `src/semconv.ts:49` (a census site); - `verify`: **3 sites**: - `src/harness.ts:580` (a census site); - `src/erasure-transaction-authorization.test.ts:163` and `:167`. These are test-file comments, which the census defers. Stages 1 to 12 took test comments too. Only comments changed: **4 lines out, 4 in**. Every one of them is a site, with no reflow and no companion line. Every touched file keeps its line count (178 / 955 / 185 at base and head), so no line citation into these files moves. **No citation number is added.** The only tracker numbers on added lines are `objectstack-ai#9650` and `objectstack-ai#9835` at `semconv.ts:49`. The removed line already carried both, and both answer 200. No PR number stands on an added line. No ADR or ruling-record file records any of the three decisions. A grep of `docs/adr/` and `scripts/adr-anchors/` for the 3 numbers, the 3 shas, `afterResponse` and `http_request_duration_ms` reads 0 hits; the control number `7329` reads 1 file in the same tree. So all three anchors are commits. **One `patch` changeset**, for `@objectstack/observability` only (`.changeset/20594-observability-provenance-anchors.md`). Its rewritten `//` line reaches the published `dist`. The `verify` rewrites leave `verify`'s `dist` byte-identical, so `verify` takes no changeset. Both results are measured below. ## Census, before and after, one package at a time **Instrument:** the gate's own `node scripts/check-issue-citations.mjs --census --json`, read-only and unchanged. The count is its `allocated-but-absent` findings under each package's path. Both runs enumerated the whole board (187 pages). | package | before (base `22e584c9db`) | after (head `62e556317c`) | |---|---|---| | `packages/observability` | **1** (`src/semconv.ts:49`, `objectstack-ai#10004`) | **0** | | `packages/verify` | **1** (`src/harness.ts:580`, `objectstack-ai#10943`) | **0** | | whole repository | 752 | 750 | | run | tree | when (UTC) | board | |---|---|---|---| | before | base `22e584c9db` | 2026-09-30 10:33:12 to 10:37:06 | frontier objectstack-ai#20838, 18,665 numbers | | after | head `62e556317c` | 2026-09-30 10:49:43 to 10:53:16 | frontier objectstack-ai#20839, 18,666 numbers | The whole-repo drop of 2 is exactly these two sites. A site-by-site diff of the two JSON outputs has 2 findings gone (`semconv.ts:49`, `harness.ts:580`) and 0 added. The other three tallies are equal in both runs: 33,155 citations that answer 200, 1,985 that answer as pull requests, and 1,018 cross-repo. **Supplementary scan (census-invisible spellings, test files and files outside `src/`).** The scan covers every `#N` token (two to six digits) in the two packages' 53 tracked files, `CHANGELOG.md` excluded, and probes each by REST (2026-09-30 10:34Z, re-probed 10:58Z). - It finds **90 distinct numbers**: 12 in `observability` and 79 in `verify`, with 1 shared. 86 answer 200 and 4 answer 404: `objectstack-ai#10004`, `objectstack-ai#10943`, `objectstack-ai#11477` and `objectstack-ai#15145`. - Dead occurrences in `src/`: 1 in `observability` and 3 in `verify` at base, 0 and 0 at head. None of them was in a string literal. At head all 232 `#N` tokens under the two `src/` trees answer 200. - Spellings the census cannot see: - `#N-word`: 0. - `#A/#B`: 4 sites. Only `semconv.ts:49` held a dead member. - `option #N` or `clause #N`: 0. - `word-#N`: 1, the `pre-objectstack-ai#11477` at `:167`, rewritten. - URL forms (`issues/N`, `pull/N`): 0. The only `github.com` strings are the two `package.json` `repository`/`bugs` URLs. - A re-grep of the four numbers with no word-boundary operator finds only `verify/tsconfig.test.json:1` (see Acceptance notes). A control of the same shape, `objectstack-ai#9835`, reads 2 lines of `semconv.ts`. ## Per-number table `git blame` at the base (on a full, unshallowed history) ties each line to the commit that wrote it. That commit's message and diff were read to decide the anchor. | number | sites (file:line) | anchor | what that commit decided | |---|---|---|---| | `objectstack-ai#10004` | `observability/src/semconv.ts:49` | `1e050a5b1` | `http_request_duration_ms` is emitted from the `IHttpServer.afterResponse` transport seam, so p95 latency sees every inbound surface. It is the squash of the pull request numbered `objectstack-ai#10004`, and the line cites it beside the two live numbers for the same seam move. The line was written by `914c41302` (the `http_request_errors_total` retirement), whose changeset, now the released entry at `observability/CHANGELOG.md:985`, cites `objectstack-ai#10004` for this same seam move. | | `objectstack-ai#10943` | `verify/src/harness.ts:580` | `46d34ab7c` | The host importer's undeclared fallback resolves from the caller's base, not from `@objectstack/types`, and `bootStack` hands in `(s) => import(s)`. The line blames to this commit. Stages 3 and 4 used the same anchor for the same number in `cli` and `types`. | | `objectstack-ai#11477` | `verify/src/erasure-transaction-authorization.test.ts:163`, `:167` | `6dd3e6968` | `/admin/remove-user` gets the raw-mount shading whose `gateAdmin` runs before the break-glass guard (ruled option A, as its message records), so a plain member hears `PERMISSION_DENIED`. Both lines blame to this commit. Its squashed message includes the `test(verify)` step that rewrote this pin. The `plugin-auth` stage used the same anchor for the same number. | All three shas were checked the same way: - Each has exactly 1 match under `rev-parse --disambiguate`. - Each is a commit with one parent. - `merge-base --is-ancestor` of each against base `22e584c9db` exits 0, on a history that is not shallow (`--is-shallow-repository` false). - The control legs: `1e050a5b1^` against the base exits 0, and base-as-ancestor-of-`1e050a5b1` exits 1. ### Wording per site - `semconv.ts:49`: `(objectstack-ai#9650 / objectstack-ai#9835 / objectstack-ai#10004)` becomes `(objectstack-ai#9650 / objectstack-ai#9835 / commit 1e050a5)`. Precedents for a mixed list: `cli/src/commands/lint.ts:915`, `mcp/src/plugin.ts:8`. - `harness.ts:580`: `objectstack-ai#10943:` becomes `Commit 46d34ab:`. The neighbouring `objectstack-ai#4700:`, `objectstack-ai#4719:` and `objectstack-ai#17911:` labels answer 200 and stay. - `erasure-transaction-authorization.test.ts:163`: `objectstack-ai#11477 (maintainer-ruled option A):` becomes `Commit 6dd3e69 (maintainer-ruled option A):`. - `erasure-transaction-authorization.test.ts:167`: `the pre-objectstack-ai#11477 route` becomes `the route before that commit`, which refers back to `:163` four lines up. ## Does the rewrite reach `dist`? Measured per package Both packages were built at base `22e584c9db` (closure plus package, `pnpm --workspace-concurrency=2 --filter '@objectstack/verify...' --filter '@objectstack/observability...' build`, VERDICT 0) and again at head `5a144efc39` (VERDICT 0). All 6 `dist` files of each package were compared byte for byte. - **`observability`: reaches `dist`.** - `index.js` and `index.cjs` differ in exactly 1 line each: the `semconv.ts:49` comment, which esbuild keeps inside the `SEMCONV` object literal. - `index.d.ts`, `index.d.cts` and both maps are equal. - The base `dist` carried `objectstack-ai#10004` in `index.js` and `index.cjs`. - ⇒ a `patch` changeset. - **`verify`: does not reach `dist`.** All 6 files are byte-equal at base and head, and the base `dist` carried none of the three numbers. ⇒ no changeset. - **Code-mutation control for `verify`,** which proves that "equal" was a measurement and not a stale build. - The mutation went through `scripts/ablation-replace.mjs` in wrap mode. The anchor `const organizationsPkg = opts.organizationsPackage` went 1 to 0, and the marker `ABLMARK20594S13` went 0 to 1 in the source. The blob moved `47a921ad0b` to `3f54cdebfe`. - After a rebuild, `ablation-dist-preflight` found the marker in `dist/index.js` and `dist/index.cjs`. `index.js`, `index.cjs` and both maps differ from the head build, and both `.d.ts` files are equal. - The restore leg: the restored blob equals HEAD `47a921ad0b` and `git diff HEAD` is empty. After a rebuild, `preflight --absent` reads the marker absent from all 6 files with a clean tree, and all 6 files are byte-equal to the head build. - The first control attempt used an anchor that was a prefix of its own replacement. The tool refused it (anchor count 1 to 1) and restored the file, so no build ran on it. - The whole workspace build, `pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=2`, finished 71/71 (all cache hits). The `dist` of both packages is byte-equal to the head build. ## Token guard The guard compared TypeScript 6.0.3 parser leaf tokens (`getChildren`, JSDoc nodes excluded) of the 3 source files at base `22e584c9db` and at `5a144efc39`, over 3,523 base tokens: - **the real diff: 0 files differ**; - comment-insertion control: 0 differ; - code-insertion control: 3 of 3 differ; - string control (first character of the first string literal): 3 of 3 differ, first differing kind `StringLiteral`. The script exited 0, and its controls ran in memory only. A first attempt with a bare scanner was discarded: it misaligns inside template literals and reported a false difference. ## Tests, typecheck, lint and gates (head `62e556317c`) - **`observability` tests:** `pnpm --filter @objectstack/observability exec vitest run --maxWorkers=2` → Test Files 7 passed (7), Tests 85 passed (85). - **`verify` tests:** `pnpm --filter @objectstack/verify exec vitest run --maxWorkers=2` → Test Files 16 passed (16), Tests 120 passed (120). The package has 16 test files, `erasure-transaction-authorization.test.ts` included. - **`verify` typecheck:** `pnpm --filter @objectstack/verify typecheck` passed, with VERDICT command-exit 0 (`tsc --noEmit`, then `check:test-typecheck` over `tsconfig.test.json`: 0 files / 0 errors). `tsc --listFiles` confirms that `tsconfig.json` reaches `harness.ts` and `tsconfig.test.json` reaches both edited `verify` files. - **`observability` typecheck:** the package has no `typecheck` script; it is a DEBT entry in `check-type-check-coverage.mjs` at 11 errors. `tsc -p packages/observability/tsconfig.json --noEmit` reads 11 errors, all in `src/__tests__/`, none in `semconv.ts`, and the program includes `semconv.ts`. `pnpm check:type-check-coverage` and `pnpm check:type-check-debt` pass (exit 0). - **`pnpm lint`:** the repo-wide `eslint . --no-inline-config` exits 0 (2026-09-30 10:54:08 to 10:57:25 UTC, at `62e556317c`). - **Gates:** `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` (run with no path) derived **60** commands, and all 60 were run with their exit codes recorded before any pipe. - 59 exited 0 on the first pass. - `pnpm check:dual-build-cjs-loads` first exited 3 (PREREQUISITE NOT MET: no `dist` for 33 packages). After the whole-workspace build it was run again and exited 0. - `--ran` reconciliation: 「60 derived, 60 run, 0 NOT-MEASURED, 0 UNRUN」, exit 0. - The diff-scoped `node scripts/check-issue-citations.mjs` judged the 2 citations this change adds (`objectstack-ai#9650`, `objectstack-ai#9835`); both answer 200, exit 0. - `pnpm check:nul-bytes` passed, and a control-byte scan of the 4 changed files reads 0. - **Main moved during the work:** `origin/main` moved 8 commits past the base (to `1741c5dcb6`). `git diff --name-only` over the two packages and the changeset path reads 0 files, so this branch was not merged forward. The merge ref CI builds covers the joint tree. ## Acceptance notes - **Left outside `src/**`, listed and not changed (a later stage of the card):** `packages/verify/tsconfig.test.json:1` cites `objectstack-ai#15145`, which answers 404. The other 14 citations outside `src/**` in the two packages answer 200: `observability/vitest.config.ts:11`, `verify/tsconfig.json:5` and `:13`, `verify/tsconfig.test.json:1`, `:2`, `:32`, `:35`, `:43` and `:60`, and `verify/vitest.config.ts:8`, `:14`, `:32`, `:63` and `:70`. `README.md` in either package carries no `#N`. - **Release-owned, not a site:** `packages/observability/CHANGELOG.md:985` carries `objectstack-ai#10004` (with `objectstack-ai#9834`) in a released entry. This PR does not edit it. - **Open-PR overlap** (read 2026-09-30 10:58Z): 13 open PRs. Only the Version Packages PR objectstack-ai#20639 touches either package, and only in `CHANGELOG.md` and `package.json`. - **Not governed:** no path is on the governed-surface register. The diff changes 19 lines, far under 5,000. --- _Generated by [Claude Code](https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #20681
Clause-②: no
What
os devhad an off branch for its watch-recompile loop (watchActive, and the loop comment's "Skipped when" list), but no argv reached it. Three changes, all inpackages/cli/src/commands/dev.ts:watchgetsallowNo: true, the declaration the siblingcompile,restartandseed-adminflags already carry.os dev --no-watchnow boots with the loop off, and bareos devkeeps it on. The decision that the loop and the stale-artifact remedy line read is now one exported function,devWatchActive, so the pin can drive it with what oclif parsed. The loop comment's--watch=falseis now--no-watch.--fail-if-no-matchto pnpm. pnpm owns the filter grammar (names, globs,./dir,...pkg), so pnpm decides whether the filter matched, and the CLI does not read the workspace a second time. This is the change that closesos dev --watch=false: oclif has no=valueform for a boolean flag, so that argv parses as--watchplus the PACKAGEfalse.--no-watchin monorepo orchestration mode is refused (exit 1, naming the flag). Change 1 also makes--no-watchreachable there. In that mode each package's owndevscript decides whether it watches, and the CLI would have printedWatch: enabledright under the flag. Before this PR that argv was refused as a nonexistent flag (exit 2), so nothing that used to work is refused now.The loud check on the PACKAGE argument fit inside
dev.ts(one token plus the refusal block), so this PR delivers the whole card. Nothing is left over.Measured
Before, at BASE
73155fedc, through the source entry (tsx packages/cli/bin/run-dev.js), in a temp dir holding onlyobjectstack.config.ts:os dev --no-watchprintedNonexistent flag: --no-watchand exited 2.os dev --watch=falseprintedPackage: false,Watch: enabled,$ pnpm --filter false dev, thenNo projects found in ..., and exited 0.After, at
4804d8a7a:os dev --watch=falseranpnpm --filter false --fail-if-no-match dev, which printedNo projects found in .... The CLI then printed✗ Development mode failed: Command failed: pnpm --filter false --fail-if-no-match devand exited 1.os dev nonexistent-pkg-xyzprintedNo projects matched the filtersand exited 1.pnpm-workspace.yaml, no config),os dev --no-watchwas refused, naming--no-watch, and exited 1.os dev @objectstack/verifyran that package'stsc -wdev script, which was still running when the 60 s timeout stopped it (exit 124). So the added flag does not break a filter that matches.examples/app-todo,os dev --no-watch --fresh -p RANDOM:Server is ready, and nowatching ...line. Control without--no-watch:watching objectstack.config.ts, src — rebuild + restart on change.pnpm added
--fail-if-no-matchin 8.13.1, according to pnpm's own CHANGELOG at v10.31.0. It was measured here on 10.31.0 only. The changeset says monorepo mode now needs pnpm 8.13.1 or later.Tests
New pin
packages/cli/test/dev-no-watch.pin.test.ts. It spawns the source entry, so it is in the integration tier (vitest-tiers.tsfireschildProcess, entryBasename, tsxBin).Parser.parseoverDev.flags/Dev.args:--no-watchgiveswatch: false, anddevWatchActiveanswers false. Bare gives true and true.--watch=falsegiveswatch: trueandpackage: 'false'.--watch=falsein a project dir exits 1, and the✗line namesfalse. The workspace fixture'sdevscript prints a marker. Bareos devthere exits 0 and prints it (the control).--no-watchthere exits 1, its✗line names--no-watch, and the marker is absent.Runs:
pnpm --filter @objectstack/cli exec vitest run --project integration --maxWorkers=2 test/dev-no-watch.pin.test.ts: 1 file, 8/8 passed.scripts/ablation-replace.mjsin wrap mode. Each restore was proven: blob == HEAD02c1c4bb1d8e, andgit diff HEADempty. All three turned red, as predicted:--fail-if-no-matchdropped: 2 failed / 6 passed (expected +0 to be 1, and no✗line). The first attempt at this leg was a no-op. The tool refused it because the replacement was a substring of the anchor, so its count could not rise. It was re-run with a replacement that does not overlap the anchor.expected +0 to be 1, and the marker present).watchlosesallowNo: 2 failed / 6 passed (the parse throws, and the spawned run exits 2).pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2: 235 of 237 files passed (3341 tests passed, 29 skipped). The other two,published-subpath-console.pinandpublished-subpath-hook-body.pin, refused becausepackages/cli/distwas not built (a prerequisite, not a result). Afterpnpm --filter @objectstack/cli build: 2/2 files, 29/29 tests passed.pnpm --filter @objectstack/cli typecheck: exit 0.check:test-typecheck: OK ... 3 file(s) / 28 error(s)is the ledger's existing figure; the new test adds no error.node scripts/pm/dispatch-gates.mjs --commands, run without paths, derived 63 commands atc36db5507. All 63 exit 0.check:dual-build-cjs-loadsandcheck:i18n-coveragefirst exited 3 (PREREQUISITE NOT MET) and then 0 once the packages were built.--ranreconciliation:63 derived, 63 run, 0 NOT-MEASURED, 0 UNRUN.pnpm lint: exit 0 atc36db5507.The gate, lint, unit and typecheck results are on the final head
c36db5507. The pin and ablation runs were onfcf4f78e3, and the only later commit adds the changeset file.Acceptance notes
os devoptions table incontent/docs/deployment/cli.mdxlists neither-w, --[no-]watchnor--[no-]restart,--log-level,--preset,--admin-emailor--admin-password. That gap predates this PR, no pin enumerates that table, and the file is outside this card's file surface. Carrier: none.--port,--fresh,--ui,--artifacttogether with a PACKAGE, and so on) and says nothing about dropping them. Not measured here. This PR refuses only the flag it made reachable.Watch: enabledwhenever watch is on. That line describes the child packages' owndevscripts, which the CLI does not control.Generated by Claude Code