plumbbob 0.9.1 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "plumbbob",
3
3
  "displayName": "PlumbBob",
4
- "version": "0.9.1",
4
+ "version": "0.9.2",
5
5
  "description": "Attention-first build process — driver skills + a CLI that keep you the decider: guidance on the work, a latch on the record.",
6
6
  "author": { "name": "Rob McLarty", "email": "hello@robmclarty.com", "url": "https://robmclarty.com" },
7
7
  "homepage": "https://github.com/robmclarty/plumbbob#readme",
package/README.md CHANGED
@@ -292,6 +292,7 @@ Each doc answers one question — in rough reading order for a new user:
292
292
  - *How do I get a local model reviewing my steps?* → [`docs/local-model-review.md`](docs/local-model-review.md) — the ollama-reviewer example walked end to end, install to every-pause review.
293
293
  - *How do I install it, exactly?* → [`docs/install.md`](docs/install.md) — the full guide and the agent-neutral roadmap.
294
294
  - *What does the CLI underneath do?* → [`docs/cli-reference.md`](docs/cli-reference.md) — every verb, flag, exit code, and the `.plumbbob/` sidecar.
295
+ - *What are these untracked files, and why is it touching my `.git`?* → [`docs/state-and-git.md`](docs/state-and-git.md) — the state files, the `info/exclude` mechanism, and the objections answered.
295
296
  - *Something's broken.* → [`docs/troubleshooting.md`](docs/troubleshooting.md) — fixes for the common snags.
296
297
  - *Why is it built this way?* → [`docs/decisions.md`](docs/decisions.md) — the `D#` / `C#` design-decision key the source cites.
297
298
  - *How does it hang together inside?* → [`docs/architecture.md`](docs/architecture.md) — the layers and planes, for contributors (in progress).
@@ -583,3 +583,39 @@ export function excludeControl(root) {
583
583
  export function excludeSidecar(root) {
584
584
  addExcludes(root, [`${DIRNAME}/`, '.check/']);
585
585
  }
586
+ /**
587
+ * Whether info/exclude already carries `pattern` verbatim.
588
+ *
589
+ * Absent or unreadable reads as "no" — the caller's job is to write it, not to
590
+ * fail on a repo that has never been excluded.
591
+ */
592
+ function hasExclude(root, pattern) {
593
+ try {
594
+ return readFileSync(gitPath(root, 'info/exclude'), 'utf8')
595
+ .split('\n')
596
+ .some((line) => line.trim() === pattern);
597
+ }
598
+ catch {
599
+ return false;
600
+ }
601
+ }
602
+ /**
603
+ * Re-apply the control-plane excludes on the way into a `git add -A`.
604
+ *
605
+ * `start` writes them once, but a plumbbob upgraded mid-build can add a control
606
+ * file the running session's exclude never learned — TICK, GRANT and
607
+ * handoff.json all arrived that way — and checkpoint/finish stage with `-A`,
608
+ * which would sweep the unexcluded file into the commit and ride it into the PR.
609
+ * Idempotent, so the common path is one read and no write.
610
+ *
611
+ * A fully-untracked sidecar is already covered by its blanket `.plumbbob/` line;
612
+ * layering the narrow patterns underneath would only add noise, so skip it. That
613
+ * check reads info/exclude rather than the cursor because a null cursor means
614
+ * "`--local` OR no build named" — and calling excludeSidecar on the tracked
615
+ * layout would untrack the whole artifact plane.
616
+ */
617
+ export function refreshExcludes(root) {
618
+ if (hasExclude(root, `${DIRNAME}/`))
619
+ return;
620
+ excludeControl(root);
621
+ }
@@ -11,7 +11,7 @@
11
11
  // DESIGN boundary.
12
12
  import { appendFileSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
13
13
  import { commit, findRepoRoot, headSha, isDirty, stageAll, stagePath, stagedPaths, stagedStat } from "../lib/git.js";
14
- import { activeBuild, buildFolder, buildLogPath, buildScope, bumpStepStat, checkpointsPath, clearHandoff, clearTick, hasSession, intentPath, readStats, seamPath, stampStepStat, stepPath, } from "../lib/sidecar.js";
14
+ import { activeBuild, buildFolder, buildLogPath, buildScope, bumpStepStat, checkpointsPath, clearHandoff, clearTick, hasSession, intentPath, readStats, refreshExcludes, seamPath, stampStepStat, stepPath, } from "../lib/sidecar.js";
15
15
  import { runCheck } from "../lib/check.js";
16
16
  import { checkLatch } from "../lib/latch.js";
17
17
  import { markStepDone, parseSteps } from "../lib/orient.js";
@@ -31,6 +31,10 @@ export async function checkpoint(cwd, args) {
31
31
  process.stderr.write('plumbbob: no active session. Run `plumbbob start "<title>"` first.\n');
32
32
  return 1;
33
33
  }
34
+ // Before either staging path — `-A` below, and the plan commit's folder-scoped
35
+ // add, which also leans on the excludes to skip the in-flight markers inside
36
+ // the build folder.
37
+ refreshExcludes(root);
34
38
  if (args.includes('--plan')) {
35
39
  return checkpointPlan(root, args);
36
40
  }
@@ -11,7 +11,7 @@
11
11
  import { appendFileSync, existsSync, readFileSync, rmSync } from 'node:fs';
12
12
  import { join } from 'node:path';
13
13
  import { commit, findRepoRoot, isDirty, stageAll } from "../lib/git.js";
14
- import { buildScope, checkpointsPath, clearTick, hasSession, intentPath, readStats, reportPath, resolveBuild, seamPath, setGrant, sidecarDir, spikePath, stepPath, } from "../lib/sidecar.js";
14
+ import { buildScope, checkpointsPath, clearTick, hasSession, intentPath, readStats, refreshExcludes, reportPath, resolveBuild, seamPath, setGrant, sidecarDir, spikePath, stepPath, } from "../lib/sidecar.js";
15
15
  import { conventionalSubject, withMarker } from "../lib/commitmsg.js";
16
16
  import { parseBuildScope } from "../lib/intent.js";
17
17
  /**
@@ -43,6 +43,7 @@ export function finish(cwd, args = []) {
43
43
  // commit under the `finish` subject. `--allow-empty` (via `commit`) still marks
44
44
  // the narrative endpoint when the tree is already clean, or under `--local`,
45
45
  // where the whole sidecar is git-excluded and there is nothing tracked to stage.
46
+ refreshExcludes(root);
46
47
  if (isDirty(root)) {
47
48
  stageAll(root);
48
49
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plumbbob",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "Attention-first build process: 12 skills + a CLI that keep you the decider — guidance on the work, a latch on the record.",
5
5
  "keywords": [
6
6
  "claude-code",