nawabari 0.1.0 → 0.3.0

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.
Files changed (41) hide show
  1. package/README.md +347 -5
  2. package/dist/cli.js +770 -51
  3. package/dist/cli.js.map +1 -1
  4. package/dist/contract.d.ts +9 -0
  5. package/dist/contract.js +291 -0
  6. package/dist/contract.js.map +1 -0
  7. package/dist/domain/doctor.d.ts +1 -1
  8. package/dist/domain/doctor.js +99 -90
  9. package/dist/domain/doctor.js.map +1 -1
  10. package/dist/domain/errors.d.ts +1 -1
  11. package/dist/domain/errors.js +5 -1
  12. package/dist/domain/errors.js.map +1 -1
  13. package/dist/domain/session-backend.d.ts +15 -3
  14. package/dist/domain/session-backend.js +348 -7
  15. package/dist/domain/session-backend.js.map +1 -1
  16. package/dist/domain/session.d.ts +247 -2
  17. package/dist/domain/session.js +42 -0
  18. package/dist/domain/session.js.map +1 -1
  19. package/dist/errors.d.ts +2 -2
  20. package/dist/errors.js.map +1 -1
  21. package/dist/git.d.ts +102 -0
  22. package/dist/git.js +714 -30
  23. package/dist/git.js.map +1 -1
  24. package/dist/operation-authorization.d.ts +76 -0
  25. package/dist/operation-authorization.js +41 -0
  26. package/dist/operation-authorization.js.map +1 -0
  27. package/dist/output-budget.d.ts +16 -0
  28. package/dist/output-budget.js +58 -0
  29. package/dist/output-budget.js.map +1 -0
  30. package/dist/presentation.js +76 -22
  31. package/dist/presentation.js.map +1 -1
  32. package/dist/repository-evidence.d.ts +74 -0
  33. package/dist/repository-evidence.js +7 -0
  34. package/dist/repository-evidence.js.map +1 -0
  35. package/dist/resource-claims.d.ts +87 -0
  36. package/dist/resource-claims.js +450 -0
  37. package/dist/resource-claims.js.map +1 -0
  38. package/dist/session-registry.d.ts +251 -1
  39. package/dist/session-registry.js +2094 -257
  40. package/dist/session-registry.js.map +1 -1
  41. package/package.json +2 -1
package/README.md CHANGED
@@ -24,6 +24,87 @@ git nawabari --help
24
24
  nawabari --version
25
25
  ```
26
26
 
27
+ ## Standalone machine contract
28
+
29
+ The installed CLI/JSON surface is the integration boundary. An orchestrator
30
+ must discover the contract before using the lifecycle:
31
+
32
+ ```bash
33
+ nawabari capabilities --json
34
+ nawabari --version --json
35
+ ```
36
+
37
+ Both discovery commands work without a Git repository. A compatible
38
+ installation reports `contract_id: "nawabari.standalone-execution.v1"` and
39
+ `schema_version: 1`. The capability response lists the exact commands,
40
+ result-schema versions, identity fields, and stable `failure_codes`. The
41
+ package version is release metadata; it is not a substitute for the
42
+ machine-contract identifier.
43
+
44
+ The supported standalone sequence is:
45
+
46
+ ```text
47
+ session create -> session claim(s) -> authorize/checkpoint
48
+ -> commit/push -> doctor (reconciliation) -> session close/gc
49
+ ```
50
+
51
+ The JSON envelope is one document on stdout. Success has `ok: true`, a
52
+ `command`, and the command's versioned result fields. Failure has `ok: false`,
53
+ the `command`, a stable `code`, a bounded human-readable `message`, and
54
+ optional structured `details`; JSON mode writes no decorative stderr. Exit
55
+ codes are `0` success, `2` usage, `3` rejected/unsafe operation, `4`
56
+ unavailable capability, `5` failed doctor checks, and `70` unexpected internal
57
+ failure. Consumers must use these fields and codes, never human presentation.
58
+
59
+ The result schemas expose the following identities:
60
+
61
+ | Surface | Versioned identities |
62
+ | ---------------------- | -------------------------------------------------------------------------------- |
63
+ | session lifecycle | `session_id`, `repository`, `worktree`, `branch`, `state` |
64
+ | claims | `claim_id`, `session_id`, `resource`, `mode` |
65
+ | authorization | `operation`, `allowed`, `code`, `claim_ids` |
66
+ | checkpoint evidence | `head`, `changed`, `staged`, `unstaged`, `untracked`, `in_claim`, `out_of_claim` |
67
+ | repository evidence | `session_id`, `base_revision`, `head`, `clean`, `paths.stats`, `evidence_hash` |
68
+ | bounded diff | `from_revision`, `to_revision`, `paths`, `stats`, `patch`, `evidence_hash` |
69
+ | commit/push | `commit_sha`, `remote`, `branch`, `target`, `relation` |
70
+ | reconciliation/cleanup | `clean`, `issues`, `candidates`, `cleaned`, `blocked`, `recovery_hints` |
71
+
72
+ Git subprocesses are bounded at 10 seconds and 64 KiB of output; checkpoint
73
+ evidence is bounded to 4,096 paths. `GIT_SPAWN_FAILED`, `GIT_TIMEOUT`,
74
+ `GIT_OUTPUT_LIMIT`, and `GIT_COMMAND_FAILED` remain distinct failure codes.
75
+ The local lifecycle requires Git and the repository-local registry/lock only;
76
+ it does not require Mottainai, GitHub, `gh`, network access, an LLM, or a
77
+ coding-agent runtime.
78
+
79
+ ## Read-only repository evidence
80
+
81
+ The evidence family is session-addressed and has no task, Issue, semantic, or
82
+ GitHub interpretation. It is the physical repository authority for one owned
83
+ session:
84
+
85
+ ```bash
86
+ git nawabari evidence snapshot --session "$NAWABARI_SESSION_ID" --json
87
+ git nawabari diff --session "$NAWABARI_SESSION_ID" --path src/example.ts --json
88
+ git nawabari diff --session "$NAWABARI_SESSION_ID" --path src/example.ts \
89
+ --patch --max-bytes 32768 --max-hunks 32 --json
90
+ ```
91
+
92
+ `evidence snapshot` verifies the registry's repository/worktree/branch owner,
93
+ then reuses checkpoint's exact NUL-safe Git observation for `changed`,
94
+ `staged`, `unstaged`, and `untracked` paths. It also reports canonical per-path
95
+ stats, `clean`, the current `head`, session state, and an `evidence_hash`.
96
+ New sessions persist the exact creation/base revision as `base_revision`;
97
+ legacy records that lack this field report `base_revision: null` and
98
+ `base_revision_proven: false` rather than inferring it from a mutable ref.
99
+
100
+ `diff` requires at least one explicit concrete path and never accepts a glob or
101
+ an empty repository-wide selection. Stats are returned by default. Patch text
102
+ requires `--patch` and is bounded to at most 64 paths, 64 KiB, and 128 hunks;
103
+ the caller may request smaller limits. Unrepresentable Git observations fail
104
+ with `GIT_STATE_AMBIGUOUS`; a requested path whose stat is not exposed by Git
105
+ remains in the result with `available: false` and makes snapshot evidence
106
+ `complete: false`, so no path silently disappears.
107
+
27
108
  ## Session lifecycle
28
109
 
29
110
  Session IDs are generated automatically as UUIDv7 values. They are immutable
@@ -41,6 +122,11 @@ git nawabari gc --dry-run --json
41
122
  git nawabari doctor --json
42
123
  ```
43
124
 
125
+ `status --json` reports the resolved `managed_worktree_root` used when
126
+ `session create` omits `--worktree`. `session create --help --json` describes
127
+ all four create options as optional and reports defaults for branch, worktree,
128
+ base (`HEAD`), and label.
129
+
44
130
  `session create` provisions a dedicated worktree and mutable branch atomically
45
131
  under the repository-scoped mutation lock. The default/integration worktree
46
132
  and its protected branch cannot be session resources. `session id` and the
@@ -52,7 +138,96 @@ state, and commits not proven reachable from the integration branch block
52
138
  destructive cleanup. A clean close releases only the owned worktree and
53
139
  branch, and repeating close is idempotent. `gc` detects stale or interrupted
54
140
  sessions; `--apply` uses the same close safety checks and reports blocked
55
- sessions instead of guessing.
141
+ sessions instead of guessing. `gc --dry-run` performs the same non-mutating
142
+ cleanup preflight and includes stable blocker codes and `recovery_hints` for
143
+ every candidate that is not safe. Cleanup revalidates the physical worktree,
144
+ branch, and `HEAD` observations immediately before each destructive Git
145
+ operation.
146
+
147
+ Routine `session list` and `status` output excludes `closed` history and is
148
+ limited to 64 records. Use `--all` (or `--history`) for an explicit complete
149
+ history view; closed records remain persisted and are never silently deleted
150
+ by listing or cleanup.
151
+
152
+ `gc` stale eligibility is separate from closed-history retention. Its default
153
+ threshold is 24 hours (`86,400,000` ms), measured from persisted `updated_at`;
154
+ records already in `stale` or `closing` state are eligible, and an otherwise
155
+ live record is also eligible when Git reports its registered worktree as
156
+ missing or prunable. Physical Git/worktree state is authoritative for that
157
+ check. `gc --dry-run` and `gc --apply` do not treat a closed record as a stale
158
+ cleanup candidate.
159
+
160
+ `doctor` includes a non-destructive `reconciliation` check. It reports
161
+ registry/Git ownership drift, including missing or prunable worktrees and
162
+ unregistered physical worktrees, without repairing or deleting anything.
163
+
164
+ ## Session resource claims
165
+
166
+ Resource claims are versioned, session-scoped ownership records stored in the
167
+ same repository registry and protected by the same mutation lock. They are
168
+ caller declarations; Nawabari does not infer them from task text or source
169
+ code. Claim JSON exposes `schema_version`, `claim_id`, `session_id`, the
170
+ repository/worktree identities, canonical `resource`, `mode`, and timestamps.
171
+ The claim schema version is `2` and supports `read`, `write`, and
172
+ `exclusive-write`. Schema v1 records use different overlap semantics and are
173
+ not interpreted implicitly: an embedding caller must explicitly run
174
+ `SessionRegistry.migrate()` before using them.
175
+
176
+ ```bash
177
+ git nawabari session claim --session "$NAWABARI_SESSION_ID" \
178
+ --resource 'src/**/*.ts' --mode read --json
179
+ git nawabari session claims --session "$NAWABARI_SESSION_ID" --json
180
+ git nawabari session update --session "$NAWABARI_SESSION_ID" \
181
+ --resource 'src/**/*.ts' --mode write --json
182
+ git nawabari session release --session "$NAWABARI_SESSION_ID" --json
183
+ ```
184
+
185
+ The modes have these normative meanings:
186
+
187
+ - `read`: a non-mutating access declaration. It is not a consistency lease,
188
+ so it may overlap an ordinary `write` claim.
189
+ - `write`: ordinary source-modification authority. It may overlap `read`, but
190
+ not another writer or any `exclusive-write` claim.
191
+ - `exclusive-write`: stronger ownership-sensitive mutation authority. It
192
+ excludes every overlapping claim, including `read`.
193
+
194
+ Overlapping claims use this complete compatibility matrix; non-overlapping
195
+ claims are compatible for every mode:
196
+
197
+ | existing \/ requested | read | write | exclusive-write |
198
+ | --------------------- | ---------- | ---------- | --------------- |
199
+ | read | compatible | compatible | conflict |
200
+ | write | compatible | conflict | conflict |
201
+ | exclusive-write | conflict | conflict | conflict |
202
+
203
+ Claims use canonical repository-relative POSIX paths. Literal path segments,
204
+ `*`/`?` segment wildcards, and a complete `**` segment are supported. Empty,
205
+ `.`/`..`, absolute, drive-relative, backslash, unsupported-glob, and
206
+ symlink-escaping forms are rejected with stable machine-readable codes.
207
+ Equivalent claim acquisition and release retries are idempotent. Closing or
208
+ garbage-collecting a session releases its claims; no separate claim registry
209
+ or claim lock exists. Claims describe ownership state only and do not provide
210
+ OS-level filesystem observation or a filesystem sandbox.
211
+
212
+ An ordinary source change uses `write` and can proceed while another session
213
+ holds a `read` declaration:
214
+
215
+ ```bash
216
+ git nawabari session claim --session "$NAWABARI_SESSION_ID" \
217
+ --resource src/example.ts --mode write --json
218
+ git nawabari authorize --session "$NAWABARI_SESSION_ID" \
219
+ --operation source-write --resource src/example.ts --json
220
+ ```
221
+
222
+ A stronger ownership-sensitive operation uses `exclusive-write` and therefore
223
+ requires no overlapping claim:
224
+
225
+ ```bash
226
+ git nawabari session claim --session "$NAWABARI_SESSION_ID" \
227
+ --resource src/example.ts --mode exclusive-write --json
228
+ git nawabari authorize --session "$NAWABARI_SESSION_ID" \
229
+ --operation commit --resource src/example.ts --json
230
+ ```
56
231
 
57
232
  ## Ownership guard
58
233
 
@@ -70,10 +245,11 @@ decision=$(git nawabari guard --session "$NAWABARI_SESSION_ID" --json) || {
70
245
 
71
246
  An allowed decision has `allowed: true` and `code: "ALLOWED"`. A denied
72
247
  decision has `allowed: false`, a stable code such as
73
- `WORKTREE_OWNED_BY_OTHER_SESSION`, `PROTECTED_WORKTREE`, or
74
- `OWNERSHIP_MISMATCH`, and a non-zero exit status. Detached, corrupt, missing,
75
- or conflicting state fails closed. The guard does not install hooks and does
76
- not prevent direct filesystem writes outside Nawabari.
248
+ `WORKTREE_OWNED_BY_OTHER_SESSION`, `PROTECTED_WORKTREE`, `DETACHED_HEAD`,
249
+ `WORKTREE_MISMATCH`, or `OWNERSHIP_MISMATCH`, and a non-zero exit status.
250
+ Detached, corrupt, missing, or conflicting state fails closed. The guard does
251
+ not install hooks and does not prevent direct filesystem writes outside
252
+ Nawabari.
77
253
 
78
254
  ## Orchestrator integration
79
255
 
@@ -95,6 +271,149 @@ The orchestrator owns scheduling, prompts, and worker lifetime; Nawabari owns
95
271
  only local session identity, worktree/branch ownership, and safe cleanup. No
96
272
  Mottainai, GitHub, `gh`, network, or agent-runtime dependency is required.
97
273
 
274
+ Mottainai is one optional caller of this contract, not a runtime dependency.
275
+ It may retain task semantics, scheduling, validation policy, Issue/PR
276
+ governance, and worker lifetime. It must pass concrete local declarations to
277
+ Nawabari and retain the returned JSON identities. Nawabari does not import or
278
+ execute Mottainai/GitHub workflow code, infer claims from task text, or create
279
+ a second registry/database.
280
+
281
+ ## Claim-aware operation authorization
282
+
283
+ `authorize` is the single decision surface for a governed local operation. Its
284
+ versioned vocabulary and required claim access are:
285
+
286
+ | operation | required access |
287
+ | ----------------- | ----------------- |
288
+ | `source-write` | `write` |
289
+ | `stage` | `write` |
290
+ | `commit` | `exclusive-write` |
291
+ | `branch-mutation` | `exclusive-write` |
292
+ | `push` | `exclusive-write` |
293
+ | `cleanup` | `exclusive-write` |
294
+
295
+ The request contains a session identity, an operation, and concrete
296
+ repository-relative resources. Nawabari independently verifies the current
297
+ repository, owned worktree, branch, active session, and persisted claims;
298
+ caller-supplied labels do not weaken that decision. The JSON result is the
299
+ automation contract and reports stable allow/deny codes such as
300
+ `MISSING_RESOURCE_CLAIM`, `INSUFFICIENT_CLAIM_MODE`, `RESOURCE_CLAIM_CONFLICT`,
301
+ `INVALID_RESOURCE`, and
302
+ the existing ownership/physical-observation codes.
303
+
304
+ `INSUFFICIENT_CLAIM_MODE` means a matching claim exists but its granted mode
305
+ is weaker than the operation requires. Its bounded details identify the
306
+ resource, required access, and matching granted mode names;
307
+ `MISSING_RESOURCE_CLAIM` remains reserved for an absent matching claim.
308
+
309
+ **`authorize` returns an authorization decision only; it does NOT execute the
310
+ operation itself.** Governed commit and push execution use this same decision
311
+ path before invoking bounded Git subprocesses.
312
+
313
+ ```bash
314
+ git nawabari authorize --session "$NAWABARI_SESSION_ID" \
315
+ --operation source-write --resource src/example.ts --json
316
+ ```
317
+
318
+ Governed commit accepts only a caller-decided final message and explicit
319
+ repository-relative resources. Every resource must be covered by an active
320
+ `exclusive-write` claim; all Git-visible changed/staged paths must be in the
321
+ explicit list. JSON includes the resulting `commit_sha`.
322
+
323
+ Commit evidence distinguishes three sets: the caller's **declared/authorized**
324
+ resources (the explicit, claim-covered list a caller passed in), the
325
+ **staged** set Git reports as staged immediately before the commit runs, and
326
+ the **actual committed** set — read back from the resulting commit itself via
327
+ a bounded, NUL-safe `git diff-tree` observation, not inferred from staging
328
+ intent. The `resources` field in a successful `commit --json` result is
329
+ always the actual committed set, proven equal to or a subset of the
330
+ authorized resources; if Git staging/index drift between staging and the
331
+ commit (a hook, a concurrent process) causes the actual commit to contain a
332
+ path outside the authorized set, the result is not reported as an ordinary
333
+ successful commit — it fails with `COMMIT_RESULT_DIVERGED`, which retains the
334
+ resulting `commitSha` (the Git commit already happened) alongside the
335
+ authorized, actual, and divergent path sets for recovery/reconciliation.
336
+
337
+ ```bash
338
+ git nawabari commit --session "$NAWABARI_SESSION_ID" \
339
+ --message 'record the local change' --resource src/example.ts --json
340
+ ```
341
+
342
+ An optional `--message-pattern <regex>` validates the final message against a
343
+ caller-declared rule before Git is invoked; Nawabari does not own or infer
344
+ commit-message conventions (such as Conventional Commits) itself, so this
345
+ check runs only when a caller explicitly supplies a pattern, and a mismatch
346
+ fails with `INVALID_COMMIT_MESSAGE` before anything is staged. The pattern is
347
+ bounded to 512 characters and is evaluated before the repository lock is
348
+ acquired, so a pathological caller-supplied pattern cannot stall other
349
+ sessions' governed operations. A repository's own `commit-msg` Git hook (if
350
+ any) still runs normally, since governed commit invokes real `git commit`.
351
+
352
+ ```bash
353
+ git nawabari commit --session "$NAWABARI_SESSION_ID" \
354
+ --message 'feat: record the local change' --resource src/example.ts \
355
+ --message-pattern '^(feat|fix|docs|refactor|test|chore): .+$' --json
356
+ ```
357
+
358
+ Governed push requires explicit claim-covered resources and an explicit
359
+ `--remote`/`--branch` target. Existing upstream and local/remote relation are
360
+ inspected before mutation. A missing upstream requires `--create-upstream`;
361
+ behind or diverged history requires explicit `--force`. Force pushes use an
362
+ exact `--force-with-lease` bound to the observed remote branch SHA; non-force
363
+ pushes rely on `--no-force` without any lease option. Nawabari fetches only
364
+ the explicit remote branch into a disposable ref when local ancestry is
365
+ missing; it does not update tracking refs or fetch unrelated branches/tags.
366
+ The JSON result includes the immutable `source_sha`, explicit `target_ref`,
367
+ observed `observed_remote_sha`, and relation.
368
+
369
+ ```bash
370
+ git nawabari push --session "$NAWABARI_SESSION_ID" \
371
+ --remote origin --branch feature/example --resource src/example.ts --json
372
+ ```
373
+
374
+ `checkpoint --json` captures bounded Git-observable `changed`, `staged`,
375
+ `unstaged`, and `untracked` path sets, canonicalizes them through the same
376
+ resource model, and reports `in_claim` and `out_of_claim` paths. Checkpoint
377
+ evidence is limited to the state Git exposes at that instant. Direct
378
+ filesystem activity that is reverted, ignored, or otherwise not observable in
379
+ the Git checkpoint is outside Nawabari's guarantee; this feature is not an
380
+ OS-level filesystem monitor.
381
+
382
+ Checkpoint canonicalization fails closed: a Git-reported path that cannot be
383
+ represented as a canonical repository resource (traversal, symlink escape, or
384
+ syntax reserved for the claim/glob model) never disappears from evidence.
385
+ Checkpoint fails the whole observation with `GIT_STATE_AMBIGUOUS` instead of
386
+ silently omitting the path, so a caller can never mistake an unrepresentable
387
+ observation for a clean one. This mirrors the strictness governed mutation
388
+ already applies to the same Git-observed paths, so checkpoint evidence is
389
+ never weaker than mutation authorization.
390
+
391
+ ## Physical execution context
392
+
393
+ Nawabari treats Git and the canonical filesystem as the authority for every
394
+ governed session context. It independently observes the repository common
395
+ directory, worktree path, current branch, and current `HEAD`, then compares
396
+ those observations with the session registry. Caller-supplied paths and branch
397
+ labels are expectations only; they are never used to replace an observation
398
+ Git can make.
399
+
400
+ The shared verifier fails closed with stable registry reasons for detached
401
+ `HEAD`, missing or prunable worktrees, repository/worktree/branch mismatches,
402
+ stale or conflicting registry ownership, ambiguous Git state, and unavailable
403
+ physical observations. Git process failures remain distinct and bounded:
404
+ spawn failure, timeout, output-limit, and non-zero/unexpected exit.
405
+
406
+ Provisioning canonicalizes the managed root and every existing path segment
407
+ before invoking Git. Traversal, symlink/intermediate-segment escapes, existing
408
+ worktree paths, and existing local branches are rejected deterministically;
409
+ the repository lock serializes Nawabari provisioning and Git's own ref checks
410
+ remain the final collision authority.
411
+
412
+ An explicit `--base` ref that is empty, malformed, or does not resolve to a
413
+ commit fails with `INVALID_BASE_REF`. The bounded JSON details retain the
414
+ rejected ref, identify `HEAD` as the default recovery base, and include the
415
+ retry hint to omit `--base`; Nawabari does not enumerate or fuzzy-search refs.
416
+
98
417
  ## Repository state and concurrency
99
418
 
100
419
  The authoritative registry is stored in the repository-common Git directory at
@@ -110,6 +429,29 @@ owner is reclaimed only when the same host proves that exact process identity
110
429
  is dead. Invalid, remote, or otherwise unverifiable lock metadata is never
111
430
  stolen and fails closed so an operator can inspect or remove it deliberately.
112
431
 
432
+ ### Conformance and extraction boundary
433
+
434
+ The packed-package suite exercises the complete standalone sequence, including
435
+ cross-process claim conflicts, governed commit/push, reconciliation, retryable
436
+ cleanup, and prunable worktree recovery. Native tests additionally cover
437
+ process interruption/atomic-write recovery, partial staging or commit failure,
438
+ stale physical state, cleanup races, bounded subprocess failures, and
439
+ idempotency (`src/registry/store.test.ts`, `src/git-mutation.test.ts`,
440
+ `src/cleanup-authority.test.ts`, `src/session-lifecycle.test.ts`, and
441
+ `scripts/smoke-test.mjs`).
442
+
443
+ The relevant Mottainai #28 execution cases are mapped as follows:
444
+
445
+ - repository/worktree identity, provisioning path safety, branch collision,
446
+ symlink escape, local staging/commit/push safety, cleanup revalidation, and
447
+ reconciliation are Nawabari-native authority and tests;
448
+ - task semantics, prompts, validation evidence policy, Conventional Commit and
449
+ PR/Issue governance, GitHub operations, and agent hooks remain optional
450
+ orchestrator-only semantics and must not move into Nawabari.
451
+
452
+ Run `pnpm run test:package` to validate the exact packed tarball and its
453
+ installed CLI, or `pnpm run verify` for the complete local conformance gate.
454
+
113
455
  ## Development
114
456
 
115
457
  ```bash