leerness 1.36.185 → 1.36.187

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 (51) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.ko.md +5 -0
  3. package/README.md +14 -4
  4. package/bin/leerness.js +291 -57
  5. package/docs/e2e-observation-contract.md +51 -0
  6. package/docs/runtime-compatibility-api.md +183 -0
  7. package/docs/state-paths-api.md +92 -0
  8. package/docs/state-scopes.md +266 -0
  9. package/docs/worktree-runtime-migration.md +278 -0
  10. package/lib/claims-baseline.js +1 -1
  11. package/lib/file-leases.js +2 -2
  12. package/lib/git.js +64 -15
  13. package/lib/io.js +9 -3
  14. package/lib/migrate.js +1 -1
  15. package/lib/preview-serve.js +5 -1
  16. package/lib/pure-utils.js +20 -0
  17. package/lib/role-fallback.js +3 -3
  18. package/lib/role-store.js +2 -2
  19. package/lib/runtime-layout.js +395 -0
  20. package/lib/runtime-writes.js +166 -0
  21. package/lib/session-close.js +1 -1
  22. package/lib/state-git.js +138 -0
  23. package/lib/state-inspect.js +39 -0
  24. package/lib/state-inventory.js +69 -0
  25. package/lib/state-paths.js +59 -0
  26. package/lib/workspace-dir.js +34 -10
  27. package/package.json +10 -6
  28. package/scripts/command-flags-probe.js +1 -1
  29. package/scripts/dead-flags-probe.js +1 -1
  30. package/scripts/e2e-child-diagnostics-probe.js +143 -0
  31. package/scripts/e2e-child-diagnostics.js +33 -0
  32. package/scripts/e2e-temp-scope-probe.js +127 -0
  33. package/scripts/e2e.js +108 -27
  34. package/scripts/encoding-selftest-probe.js +222 -0
  35. package/scripts/false-claim-probe.js +34 -3
  36. package/scripts/file-lease-probe.js +5 -2
  37. package/scripts/legacy-runtime-negative-probe.js +72 -0
  38. package/scripts/mutation-integrity-probe.js +3 -0
  39. package/scripts/role-fallback-probe.js +67 -6
  40. package/scripts/runtime-admission-probe.js +282 -0
  41. package/scripts/runtime-git-write-probe.js +116 -0
  42. package/scripts/runtime-layout-probe.js +1198 -0
  43. package/scripts/runtime-repl-probe.js +271 -0
  44. package/scripts/runtime-replacement-probe.js +190 -0
  45. package/scripts/runtime-write-probe.js +458 -0
  46. package/scripts/selftest-cleanup-probe.js +168 -0
  47. package/scripts/state-inspect-cli-probe.js +209 -0
  48. package/scripts/state-scopes-probe.js +590 -0
  49. package/scripts/workspace-dir-lock-order-probe.js +310 -35
  50. package/scripts/workspace-dir-migration-probe.js +17 -2
  51. package/scripts/workspace-selection-probe.js +230 -0
@@ -0,0 +1,51 @@
1
+ # E2E observation contract
2
+
3
+ T-0182 and T-0183 correct test observation, not production runtime admission.
4
+ The original full E2E assertions and supported OS/Node matrix remain required.
5
+
6
+ ## Diagnostic helper
7
+
8
+ - childDiagnostics(result, elapsedMs, timeoutMs, parsedReport): summarize an actual child result without executing, retrying, mutating or accepting it.
9
+
10
+ ## Fields
11
+
12
+ - status: the native exit status; null remains null.
13
+ - signal: the native termination signal or null.
14
+ - errorCode: the native spawn error code, bounded to80 characters, or null.
15
+ - errno: the native numeric errno or a bounded string, or null.
16
+ - errorMessage: at most160 characters, or null.
17
+ - elapsedMs: nonnegative monotonic duration captured immediately after the child returns.
18
+ - timeoutMs: the actual child deadline.
19
+ - stdoutBytes: captured byte count; null means the stream was not captured.
20
+ - stderrBytes: captured byte count; null means the stream was not captured.
21
+ - stdoutTail: at most256 characters, or null for an uncaptured stream.
22
+ - stderrTail: at most256 characters, or null for an uncaptured stream.
23
+ - version: at most64 characters from a parsed doctor report, or null.
24
+ - mcpTools: the parsed finite numeric count, or null.
25
+ - healthy: the parsed boolean, or null.
26
+ - selftest: bounded pass/total/ok/failure count and at most five failure names of120 characters each, or null.
27
+
28
+ ## Preserved behavior
29
+
30
+ The fresh-migration handoff, doctor/commands and JSON121 blocks retain their
31
+ actual child invocation, fixture environment, success predicates and runtime
32
+ warning gate. Diagnostics are printed only on failure, including the setup
33
+ migrate/init result. An uncaptured stream is not described as empty output.
34
+
35
+ Only JSON121's doctor deadline changes:990000ms covers its nested900000ms
36
+ selftest deadline plus90000ms for environment probes and startup/teardown.
37
+ The other14 queries retain180000ms, init300000ms, the separate doc/surface
38
+ doctor300000ms, commands15000ms and fresh handoff/migrate15000/60000ms.
39
+ This is deadline consistency, not a measured speed improvement or a claim that
40
+ the historical CI handoff/doc-surface failures were caused by timeouts.
41
+
42
+ The134 leftover counter reads the same `sb` used for child TMPDIR/TEMP/TMP.
43
+ A foreign parent marker must not affect it; a real child-sb marker must fail.
44
+ Observation preserves fixture bytes/mtime and never deletes unrelated files.
45
+
46
+ ## Verification limits
47
+
48
+ The small probes execute the original assertion blocks with controlled fixtures
49
+ and actual short-lived child failure results. They prove observation behavior,
50
+ not a full product run. Separate real original-block executions and final
51
+ same-SHA CI are required. Static function/field matching is not semantic proof.
@@ -0,0 +1,183 @@
1
+ # Runtime compatibility, before migration
2
+
3
+ P-0021 / T-0180 implements compatibility checking only. Legacy data stays in place.
4
+ No command creates a descriptor, moves data, activates a runtime or finalizes a run.
5
+
6
+ ## Read-only diagnosis
7
+
8
+ ```sh
9
+ leerness state compatibility . --json
10
+ ```
11
+
12
+ Accepts one optional project path or `--path`; the explicit flag takes precedence.
13
+ Unknown/duplicate flags, blank paths and extra operands fail before bookkeeping.
14
+ Exit 0 means the observed legacy layout is compatible; exit 1 means blocked.
15
+ `state inspect` remains a separate metadata-only path/inventory operation.
16
+
17
+ ```json
18
+ {
19
+ "schema": "leerness.runtime-compatibility/v1",
20
+ "schemaVersion": 1,
21
+ "ok": true,
22
+ "compatible": true,
23
+ "writeDisposition": "allowed",
24
+ "reasonCode": "legacy_absent",
25
+ "scope": "worktree",
26
+ "observedLayout": "legacy",
27
+ "supportedWriterProtocol": 1,
28
+ "activationSupported": false,
29
+ "workspaceAdmission": "operation-start"
30
+ }
31
+ ```
32
+
33
+ The report contains bounded reason codes, not descriptor text, raw Git output, private
34
+ conversations or inferred migration success. It creates no cache, lock or presence record.
35
+
36
+ ## Reader exports
37
+
38
+ - createRuntimeCompatibilityReader(root): capture topology/admission and return a fresh observation function.
39
+ - inspectRuntimeCompatibility(root): new admission and one read-only report.
40
+ - assertRuntimeWriteAllowed(root): join writer admission, then return an allowed report or throw a bounded compatibility error.
41
+
42
+ ## Fields
43
+
44
+ - schema: `leerness.runtime-compatibility/v1`.
45
+ - schemaVersion: 1.
46
+ - ok: the observation is compatible.
47
+ - compatible: the legacy writer is admitted by this observation.
48
+ - writeDisposition: `allowed` or `blocked`.
49
+ - reasonCode: bounded stable reason, never descriptor contents.
50
+ - scope: `worktree`, `project-local`, or `unknown` when authority cannot be established.
51
+ - observedLayout: `legacy`, `unsupported`, or `unknown`.
52
+ - supportedWriterProtocol: 1.
53
+ - activationSupported: false.
54
+ - workspaceAdmission: `operation-start`.
55
+
56
+ ## Fixed authority and strict input
57
+
58
+ - Git: `<GIT_DIR>/leerness/layout.json`, fixed for the whole worktree, outside projectKey.
59
+ - Non-Git: `<projectRoot>/.leerness/cache/state-layout.json`, independent of workspace selection.
60
+ - Only the six fields described in [the migration contract](worktree-runtime-migration.md)
61
+ are accepted. Maximum 16 KiB; strict UTF-8, no BOM, duplicate/unknown keys, hard links,
62
+ unsafe parent links, special files or changed-during-read input.
63
+ - New Git `leerness/projects` (even empty), alternate legacy-workspace layout indicators,
64
+ non-Git state-runtime, or unresolved introduction of Git block legacy writes.
65
+ - Missing Git is acceptable only for positively identified non-repositories. Broken Git,
66
+ discovery overrides, ambiguous ownership and topology changes fail closed.
67
+
68
+ ## Writer operation boundary
69
+
70
+ `createRuntimeCompatibilityReader(root)` resolves one immutable Git topology snapshot.
71
+ It admits workspace ownership/selection once at operation start. It rechecks directory
72
+ types/links, topology identity, descriptor bytes and fixed runtime indicators on later reads.
73
+ It does not recursively scan runtime records or reclassify an operation's own transient files.
74
+ This distinction preserves existing init and lock-owned legacy workspace migration.
75
+ New public inspections and later writer operations make a new workspace admission.
76
+ New writer operations may read-only wait for an existing regular, single-linked legacy
77
+ workspace migration lock before classifying ownership. They reclassify after the peer
78
+ finishes; waiting never grants lock ownership or permission to overwrite a conflict.
79
+ The existing migration wait budget defaults to 10 seconds, is configurable through
80
+ `LEERNESS_WORKSPACE_MIGRATION_LOCK_WAIT_MS`, and is clamped to 0–60 seconds. Expired or
81
+ non-regular/linked lock observations block with `workspace_dir_migration_locked`; no
82
+ lock is read, reclaimed or deleted by admission. The budget bounds contention waiting,
83
+ not unrelated Git/filesystem I/O. Public `state compatibility` remains immediate.
84
+ An observed non-workspace denial is terminal even if a peer subsequently removes its
85
+ descriptor. A conflict snapshot gets at most one extra classification when no lock is
86
+ seen, covering a peer that completed between observations without accepting persistent
87
+ dual-live state.
88
+
89
+ Canonical project identities share one admitted reader across 8.3, namespaced and
90
+ junction aliases. Each target is resolved again, so retargeting an alias does not retain
91
+ stale authority. Nested operations on another project receive independent admission;
92
+ an operation never waits for its own migration lock through an equivalent path.
93
+ Standalone stores may coexist with their producers' exact lock/release, temporary,
94
+ corruption-backup and first-install names. Unknown names, links and incorrect entry
95
+ kinds remain refused; the reader does not open store or recovery contents. This
96
+ preserves concurrent role/provenance writers without accepting an arbitrary folder.
97
+ Existing standalone output directories are recognized by exact name and regular
98
+ directory kind, so a producer's first successful write does not block its next operation:
99
+
100
+ | Exact top-level directory | Existing producer |
101
+ | --- | --- |
102
+ | `previews` | Preview commands and direct store-free builder |
103
+ | `api-skills` | API document/skeleton storage |
104
+ | `skills` | Local skill learn/install |
105
+ | `personas` | Custom persona templates |
106
+ | `skills-export` | Local skill export/export-all |
107
+ | `reviews` | Review prompt `--emit md` directory |
108
+ | `incidents` | Incident JSON storage |
109
+ | `skills-publish`, `skills-publish-tarball` | Existing local publication staging |
110
+
111
+ Admission neither enumerates nor reads their contents, and does not certify domain
112
+ records, authorize publication, or bypass a producer's own checks. Linked directories,
113
+ wrong entry kinds, lookalike names and foreign siblings remain refused. The existing
114
+ E2E API collision/corruption/JSON contract is preserved without requiring `init`.
115
+
116
+ The same exact-name rule covers existing standalone files below, including their
117
+ already-defined lock/temporary/recovery shapes. Metadata recognition is not a claim
118
+ that each command can execute without its own prerequisites or user authorization.
119
+
120
+ | Exact top-level files | Existing producer |
121
+ | --- | --- |
122
+ | `user-requests.json` | Request recording |
123
+ | `shell-failures.json` | Shell analysis `--record` (not command execution) |
124
+ | `platform-constraints.json` | Custom platform constraints |
125
+ | `wakeup-history.json` | Local wakeup history/interval settings |
126
+ | `agent-slash-commands.json` | Slash-command snapshot recording |
127
+ | `environment.json` | Environment detection writeback |
128
+ | `agent-permissions.json`, `credentials.local.json` | Explicit local settings/credential registration |
129
+ | `llm-bench-history.md` | Manual benchmark recording |
130
+ | `glossary.md`, `glossary.json` | Glossary build |
131
+ | `reuse-map.md`, `design-system.md` | Reuse registration/design-guide merge |
132
+ | `skill-suggestions.md`, `skill-auto-cache.json` | Skill suggestions/official catalog cache |
133
+ | `provider-probe-cache.json`, `orchestrate-log.md` | Provider-probe cache/explicit orchestration log |
134
+ | `feature-graph.md` | Feature graph, with the existing project/explicit-force gate |
135
+ | `enforce.json` | Hook installation, still requiring Git and its installation checks |
136
+ | `agent-reminders.md` | Explicit stale-handoff writeback |
137
+
138
+ No cache refresh, provider call, server, credential operation, scheduling or publication
139
+ is performed by compatibility inspection. This is a bounded legacy-producer inventory,
140
+ not a new migration manifest or a guarantee about unregistered external writers.
141
+
142
+ `withRuntimeWrites(root, callback)` provides an operation-local synchronous/async failure
143
+ latch. CLI admission precedes automatic workspace migration and bookkeeping and uses the
144
+ handler's actual target/precedence. MCP telemetry, long-lived REPL turns, known direct
145
+ module writers, I/O paths, retained descriptors and lock heartbeat use the same reader.
146
+ Synchronous filesystem interception is active only inside writer operations; importing
147
+ the module alone does not modify the host's filesystem methods. Windows subprocess-based
148
+ replacement and mutating Git commands have explicit pre-attempt checks.
149
+
150
+ CLI startup rejection uses `runtime_layout_incompatible`; direct writer rejection uses
151
+ `E_RUNTIME_LAYOUT_INCOMPATIBLE` with `reasonCode`. Unsafe descriptor parents or ambiguous
152
+ standalone ownership are rejected before domain-specific validation. Direct read-only
153
+ store APIs retain their own specific diagnostics. Existing workspace selection errors
154
+ (such as conflicting live workspaces) retain their established CLI error contract.
155
+
156
+ `pathWriter` recognizes metadata paths; callers writing arbitrary project files must carry
157
+ their project operation explicitly. Raw external editors, subprocesses, old installed hooks,
158
+ other clones and unsupported external writers are not fenced by this API. A caught guard
159
+ error must not become successful completion. Already prepared recovery material is retained
160
+ if later incompatibility prevents cleanup; do not automatically delete it or assume rollback.
161
+
162
+ Topology is reused to avoid repeated Git processes; compatibility itself is never a stale
163
+ cache. A 50-read fixture used one Git query and one workspace enumeration after admission,
164
+ versus 50 enumerations in the first implementation. This is an I/O count, not a throughput benchmark.
165
+
166
+ ## Limits and verification
167
+
168
+ An external change between the final check and actual write is still possible. Generation
169
+ is not a fencing token. Do not activate a future layout under A-only clients. Real v1.36.186
170
+ CLI and already-loaded writer controls demonstrate that older clients ignore the descriptor.
171
+ Phase B requires separate approval, stopped/upgraded participants, a writer admission
172
+ protocol and verified recovery/retention. None is supplied by an `allowed` report.
173
+
174
+ `npm run test:runtime-compatibility` runs reader, bounded admission/alias/denial controls,
175
+ real CLI/MCP/module/lock/FD, Git transport,
176
+ Windows replacement and encoding-diagnostic probes. Windows-only controls are not implied
177
+ by POSIX results. Release-only old-client controls require an existing installation:
178
+
179
+ ```sh
180
+ node scripts/legacy-runtime-negative-probe.js /path/to/installed/leerness-1.36.186
181
+ ```
182
+
183
+ The probe does not install a package and checks the old installation's source remains unchanged.
@@ -0,0 +1,92 @@
1
+ # State inspection API v1
2
+
3
+ Available in v1.36.186. T-0174 / P-0020 is an additive inspection-only foundation.
4
+ It does not move data or activate a new backend. The complete staged design is in
5
+ [State scopes and migration](state-scopes.md).
6
+
7
+ ## CLI
8
+
9
+ ```sh
10
+ leerness state inspect
11
+ leerness state inspect ./project --json
12
+ leerness state inspect --path ./project --language en
13
+ ```
14
+
15
+ The target must be an existing directory. `--path` wins over the optional positional
16
+ target; otherwise the target is the current directory. Blank paths, extra positionals,
17
+ duplicate `--path`, unknown options and unsupported write flags fail before discovery.
18
+ Text defaults to Korean; `--language` or `LEERNESS_LANG` chooses English/Korean without
19
+ reading a project manifest. JSON is one document, with `ok: true` on success, or the
20
+ standard `ok: false` / `code` / `error` envelope and exit 1 on failure.
21
+
22
+ ## Module export
23
+
24
+ - `resolveStatePaths(root, options)` from `lib/state-paths.js`: read-only resolution;
25
+ default root is cwd. `options.env` supplies the child Git environment and workspace
26
+ override; `options.envValue` overrides workspace selection. Neither is persisted.
27
+
28
+ `inspectState(root, options)` from `lib/state-inspect.js` adds `ok` and the fixed-list
29
+ inventory; `formatStateInspection(report, language)` renders the already resolved
30
+ snapshot without more I/O. The resolver does not initialize the CLI or discover skills.
31
+
32
+ ## Fields
33
+
34
+ - schemaVersion: 1.
35
+ - schema: `leerness.state-paths/v1` (inspection: `leerness.state-inspection/v1`).
36
+ - activeLayout: `legacy`; existing writers remain authoritative.
37
+ - runtimeActivated: false.
38
+ - migrationAvailable: false.
39
+ - projectRoot: canonical absolute directory of the requested project, not necessarily the Git root.
40
+ - projectKey: `project-` plus SHA-256 of the canonical repository-relative project path.
41
+ - projectRelativePath: forward-slash relative path, or `.` for a repository root/non-Git project.
42
+ - git: absolute worktreeRoot/gitDir/gitCommonDir and linkedWorktree, or null for genuine non-Git.
43
+ - workspace: selected name/path, canonical path, detectedPaths, legacy presence and recognition status.
44
+ - scopes: exactly project, worktree, commonControl, immutableRecord and generatedView.
45
+ - warnings: explicit non-Git, unrecognized/foreign and legacy-workspace observations.
46
+
47
+ All `proposedPath` / `proposedPaths` values are future addresses, **not created, activated,
48
+ validated as writable, or authorization to write**. Project scope separately reports
49
+ `currentPath`. Inventory rows report the actual legacy path, classification, proposed
50
+ semantic scope, migrationAvailable=false and lstat metadata (including links/errors).
51
+ They are not a move plan. Mixed task/context/queue documents must be split by field;
52
+ human intent is not disposable generated state.
53
+
54
+ Inventory covers the selected path plus any other existing canonical/legacy workspace,
55
+ with workspacePath and selectedWorkspace on each row. A legacy reader selection must not
56
+ hide canonical runtime, nor may a canonical selection hide residual legacy evidence.
57
+ Text output prefixes rows with the workspace directory name; contents are never merged.
58
+
59
+ Project identity is independent of branch name and session. Matching repository-relative
60
+ projects in linked worktrees share a common-control proposal, but never private runtime.
61
+ Siblings differ. Separate clones/hosts do not share live control. Non-Git proposals use
62
+ project-local `cache/state-runtime`, with commonControl unavailable, not an invented shared store.
63
+ These keys are namespaces, not credentials. Moving a project inside a repo changes its key.
64
+
65
+ ## Failures and limits
66
+
67
+ Path errors distinguish invalid/not-found/not-directory/unreadable targets. Git errors
68
+ distinguish missing, unsupported, bare, metadata-directory, timeout, output-limit,
69
+ unreadable, ambiguous output and repository discovery failure. Filesystem
70
+ errors in Git-returned paths or the discovery marker walk remain Git-specific;
71
+ they never misreport an existing inspection target as missing. Existing workspace
72
+ conflict/override/link errors retain their codes; strict inspection adds workspace_unreadable.
73
+ Git failure never silently falls back to a different backend. Existing discovery ceilings
74
+ are respected; nonexistent ceilings are ignored as Git does. Git location/config injection
75
+ overrides are sanitized through the existing gateway, not through a duplicate subprocess wrapper.
76
+
77
+ One Git topology query has a 5-second process timeout and 64-KiB output cap (Windows executable
78
+ discovery is a separate bounded gateway step). Git must support `--path-format=absolute`.
79
+ Workspace discovery reads immediate directory names; inventory uses a fixed set of leaves
80
+ and cached parent metadata without contents or recursive traversal. Links are not followed
81
+ for inventory. This is an observational, non-transactional filesystem snapshot; it is not
82
+ record validation, tracking detection, filesystem locking or a migration-integrity certificate.
83
+
84
+ ## Verification
85
+
86
+ `npm run test:state-scopes` runs selector equivalence, topology/metadata and real CLI tests.
87
+ The CLI test instruments normal execution without internal/no-migration/no-stale bypasses:
88
+ one Git query, no npm/provider command, no project content read, no write; it also compares
89
+ all fixture/cwd/Git file bytes and mtimes. Windows may perform trusted Git location and
90
+ console encoding operations. No new runtime dependency, persistent config or MCP tool is added.
91
+ Invalid option diagnostics are instrumented too: neither target nor unrelated cwd
92
+ manifest contents are read merely to choose an error language.
@@ -0,0 +1,266 @@
1
+ # State scopes and migration design
2
+
3
+ Status: scope inspection implemented; runtime architecture remains proposed (2026-09-05).
4
+ Request: UR-0097; implementation/optimization approval: UR-0098. Audit: T-0173.
5
+ First implementation: T-0174 / approved P-0020. See [the inspection API](state-paths-api.md).
6
+
7
+ **Path inspection and an approved compatibility-only write boundary are implemented; stores and migration below remain designs.**
8
+ The first implementation covers path resolution and read-only inspection. P-0021 adds
9
+ `state compatibility` and observed-layout rejection; see [its API](runtime-compatibility-api.md).
10
+ Existing data locations and task IDs stay unchanged. No migration or activation is available.
11
+
12
+ ## 1. Evidence from the current implementation
13
+
14
+ Audited baseline: v1.36.185, commit `c84ab03b5b5d385985ee2797a95c02c7d4004be4`.
15
+
16
+ | Surface | Existing source / storage | Structural consequence |
17
+ |---|---|---|
18
+ | Project directory | `lib/workspace-dir.js` resolves `.leerness` and migrates `.harness` | The baseline had no scope resolver. T-0174 adds one without replacing this migration; a pure selector reuses the existing inspection snapshot. |
19
+ | Session presence | `lib/session-presence.js` predicates and `bin/leerness.js` I/O use `.leerness/cache/sessions/` | Sessions have addresses and ignored runtime files already, but different worktrees cannot see each other's presence through this store. |
20
+ | Handoff freshness | `bin/leerness.js`: `.leerness/cache/handoffs/` | Session-bound freshness markers are consumed by handoff/enforcement; migrate those consumers together. |
21
+ | REPL conversation sessions | `bin/leerness.js`: `.leerness/cache/agent-sessions/<sessionId>.jsonl` | This is a separate local conversation surface, not presence. Do not promote raw conversations into public audit records. |
22
+ | Source-file lease | `lib/file-leases.js`: `.leerness/cache/file-leases.json` | Exact-file TTL coordination is local to a checkout; it is not a repository-wide task claim. |
23
+ | State substrate | `bin/leerness.js`: `.leerness/state.json`, `runs/run-N.json`, `handoff/` | Session ownership fixes same-checkout cross-talk, but counters and mutable run files remain local branch files rather than globally unique completion records. |
24
+ | Decisions and lessons | `_saveDecisions` / `_saveLessons`: JSON arrays plus Markdown projections | Valid JSON arrays take precedence. Current readers silently fall back to Markdown for invalid JSON/non-arrays; migration must not. Both the array and its projection are rewritten, so independent records still change the same tracked files on separate branches. |
25
+ | Task progress | `readProgressRows` / `writeProgressRows`: `.leerness/progress-tracker.md` | The table is currently authoritative, not an event-derived view. Reclassifying it without migrating all readers and writers would lose task updates. |
26
+ | Session close | `lib/session-close.js` reads task rows, writes handoff, updates auto-marked current-state lines, invokes further bookkeeping | File locks serialize local writes, not Git merges or a whole multi-file snapshot. The first in-progress task selects the recommendation, which need not be the current session's task. |
27
+ | Execution provenance | `lib/role-fallback.js`: `.leerness/execution-ledger.jsonl` | Revision-bound role/fallback evidence exists, but this checkout ignores the ledger. It is not a durable Git audit record. |
28
+ | Runtime-like tracked files | `git ls-files` includes `active-wakeups.json`, `auto-resume-plan.json`, `next-action-queue.json`, `pre-wake-report.json`, `last-handoff.json`, `routing-log.json` | Ignoring `cache/` alone does not remove mutable operational state from merge inputs. Classification must be per surface, not extension-based. |
29
+ | Integrity checks | `lib/state-integrity.js` inspects only immediate `.leerness/*.json` parseability | Moving to nested stores needs explicit schema/record coverage; a green legacy scan cannot attest to new scopes. |
30
+
31
+ Some fields mix intent with execution: a queued next action or resume plan may contain
32
+ user-authored instructions. Preserve those as project/task inputs, and move only execution
33
+ status/cursors to runtime. Never classify an entire mixed file as disposable cache.
34
+
35
+ ## 2. Three storage layers, five semantic scopes
36
+
37
+ The three physical layers are **versioned project data**, **worktree-private execution**,
38
+ and **repository-common coordination**. Immutable-Record and Generated-View are semantic
39
+ scopes within that topology, not two additional database servers.
40
+
41
+ | Scope | Proposed location | Authority and writer |
42
+ |---|---|---|
43
+ | Project | `<project>/.leerness/` and optional `config/` | Versioned policy, role definitions, project identity, task specifications and human-authored context; explicit project mutations only. |
44
+ | Worktree | `<gitDir>/leerness/projects/<projectKey>/runtime/` | Per-session/per-run execution state. One owner per mutable session record; no branch name as identity. |
45
+ | Common-Control | `<gitCommonDir>/leerness/control/projects/<projectKey>/` | Claims, leases, dependencies, registered worktrees, fencing revisions and run status. All writes pass the same ControlStore transaction boundary. |
46
+ | Immutable-Record | `<project>/.leerness/memory/{decisions,lessons}/` and `.leerness/records/runs/<runId>/` | Individual durable knowledge/result/review records. Exclusive creation; corrected decisions link `supersedes`, never silently overwrite. |
47
+ | Generated-View | `.leerness/generated/` for canonical projections; private runtime `views/` for live session views | Deterministic output with input digests and schema/generator versions. Canonical summary is written only by the consolidator. Live view is local and ignored. |
48
+
49
+ The main worktree commonly has `gitDir === gitCommonDir`. Distinct `runtime/` and
50
+ `control/` namespaces are therefore mandatory even there. A session subdirectory is still
51
+ required: two agents can operate in one worktree.
52
+
53
+ The audited repository confirmed the linked checkout's Git directory ends in
54
+ `.git/worktrees/t0165-convergence`, while the main checkout and both common-directory
55
+ queries resolve to the main `.git`. This matches the [Git worktree documentation](https://git-scm.com/docs/git-worktree#_details).
56
+ Use Git's [absolute path queries](https://git-scm.com/docs/git-rev-parse#_options_for_files),
57
+ not string concatenation with `<project>/.git`, which can be a gitfile.
58
+
59
+ ### Identity and unsupported environments
60
+
61
+ - Resolve topology through the existing `lib/git.js` environment-sanitizing, shell-free
62
+ gateway. Resolve the worktree root separately from the Leerness project root.
63
+ - Preserve existing workspace markers and foreign-directory detection. A directory with
64
+ only new `config/memory/records/generated` children is not automatically recognized by
65
+ today's workspace classifier; a versioned marker/compatibility update is required.
66
+ - P-0020 uses a deterministic project key derived from the canonical repository-relative
67
+ project path (`.` for the root); identical subprojects across linked worktrees match,
68
+ sibling monorepo projects do not. A future explicit project ID can preserve identity
69
+ across project moves. Neither ID is a credential or an authorization boundary.
70
+ - Resolve Windows case/alias/junction and symlink containment before writing. Reject
71
+ ambiguous mappings instead of silently sharing or relocating another project's state.
72
+ - Bare repositories, broken gitfiles, permission failures, unsupported Git and Git absence
73
+ must be distinguishable. Git errors do not silently select a different storage backend.
74
+ - True non-Git projects retain an explicitly labelled, ignored project-local runtime
75
+ fallback; they have no cross-worktree coordination guarantee. Inspection creates nothing.
76
+ - Submodules use their own Git topology. Separate clones and separate hosts do not share
77
+ `gitCommonDir`; remote coordination is out of scope. Network filesystems need a separate
78
+ locking guarantee before they are supported for common control.
79
+
80
+ ## 3. Module boundaries and reuse
81
+
82
+ The inspection modules exist now. Store/facade/view names below remain planned contracts:
83
+
84
+ | Module | Responsibility | Existing component to reuse |
85
+ |---|---|---|
86
+ | `lib/state-paths.js`, `lib/state-git.js` (implemented) | Five-scope map, canonical project namespace and read-only Git topology; no activation | `lib/git.js`, one `lib/workspace-dir.js` snapshot + pure selection without migration |
87
+ | `lib/state-inventory.js`, `lib/state-inspect.js` (implemented) | Fixed known-surface metadata inventory and CLI JSON/text projection; no recursive content scan | Existing workspace discovery; CLI exact early dispatch skips usage/migration/stale checks and unused skillpack/npm discovery |
88
+ | `lib/state-manager.js` | Narrow orchestration facade, schema dispatch, explicit migration status; no giant all-purpose store | Existing CLI/MCP adapters delegate to this facade incrementally |
89
+ | `lib/worktree-state.js` | Addressed sessions, runs, handoffs and resumable execution; explicit ownership | Session key and freshness contracts in `lib/session-presence.js`; existing run ownership rules |
90
+ | `lib/control-store.js` | Repository-scoped task claim, revision CAS and fencing; transaction ordering | Existing lock primitives after common-root/multi-process validation; no duplication of Git execution |
91
+ | `lib/memory-store.js` | Strict, bounded immutable record load/create; supersession/index projection | Existing decision/lesson parsers and renderers as legacy import adapters |
92
+ | `lib/run-record.js` | Validate execution/review envelopes, finalize and durable receipt | `lib/role-agent-schema.js`, `lib/role-store.js`, `lib/role-fallback.js` provenance contracts |
93
+ | `lib/state-views.js` | Pure deterministic rendering from an explicitly consistent snapshot | Existing Markdown renderers and dashboard serialization, preserving handwritten text |
94
+
95
+ Dependency direction: CLI/MCP → StateManager → domain stores / views → path, Git and I/O
96
+ primitives. Read-only topology inspection must not call initialization, workspace migration,
97
+ telemetry, presence registration, lease creation or providers. Avoid reusing a convenient
98
+ helper whose discovery path has write side effects.
99
+
100
+ Runtime dependencies remain zero, Node >=18. Start with bounded JSON records and existing
101
+ file-lock primitives, not a new YAML parser or SQLite package. `ControlStore` is a storage
102
+ interface; a database backend is a future evidence-driven option, not an MVP dependency.
103
+
104
+ ## 4. Concurrency and conflict rules
105
+
106
+ 1. A session owns its mutable runtime files; other sessions submit records/messages through
107
+ a store operation, never directly edit its progress or handoff.
108
+ 2. Runtime is not a Git merge input. Moving files alone is insufficient: migrate ignore
109
+ rules, old writers, hooks, telemetry, integrity checks, packaging and cleanup too.
110
+ 3. Persistent knowledge is record-oriented. Use built-in random UUIDs or an equivalently
111
+ collision-resistant scheme, not a branch-local maximum counter. Preserve legacy IDs as
112
+ qualified import metadata; never merge two unrelated `T-0174`/`run-0001` by label alone.
113
+ 4. Same ID + same canonical payload is an idempotent replay. Same ID + different payload is
114
+ `record_id_conflict` and changes no records. Create atomically without overwrite.
115
+ 5. Generated-file conflicts are resolved by regenerating from accepted inputs. Preserve
116
+ human-written sections as project context before deprecating old mixed documents.
117
+ 6. Semantic decision disagreement creates a separate decision/review, not `merge=union`.
118
+ 7. A single logical consolidator publishes canonical project summaries. A role label alone
119
+ is insufficient: validate owner, lease generation and expected revision at publication.
120
+ Expired writers cannot publish after a successor acquires ownership (fencing).
121
+ 8. Physical source-file leases and repository task claims are distinct. Two worktrees can
122
+ edit their separate copies of the same path. Intentional duplicate task execution is
123
+ coordinated by a repository task key; source leases remain keyed by physical identity.
124
+ 9. Common control transactions cannot rely on a thread holding a lock elsewhere. Define
125
+ lock ordering, bounded critical sections, stale-owner recovery and byte/row limits;
126
+ verify concurrent acquisition and crash recovery with real processes.
127
+ Existing lock ownership must not be stolen solely because a PID looks absent or a TTL
128
+ expires. A new task-claim lease and a local write mutex have different recovery contracts.
129
+ 10. Visibility is not authority: provider observations can be unknown or stale. A role or
130
+ policy revision change requires revalidation; cross-worktree policy divergence cannot
131
+ be resolved by taking whichever branch updated control most recently.
132
+
133
+ Presence currently records handoff/close observations, not a heartbeat protocol or proof
134
+ that a process is alive. Common control must distinguish observed presence, a held lease,
135
+ an expired heartbeat and unknown availability. Retain existing session identity validation.
136
+
137
+ ## 5. Run/review provenance and finalize
138
+
139
+ Each run has separate implementer, reviewer and terminal result records. Fields include:
140
+ schema version, globally unique run/task/agent identity, role, requested and actual
141
+ provider/model, observed identity source, resolution/fallback chain and reasons,
142
+ role/routing/availability revisions, branch/base/source commit, changed-file or diff digest,
143
+ commands with exit results, tests/evidence references, issues, timestamps, and next role.
144
+ Unknown model identity is explicitly unknown; never claim a specific model because it was
145
+ requested. Existing strict high-risk review-independence policy remains intact.
146
+
147
+ A review binds to the exact reviewed source commit (or explicitly identified uncommitted
148
+ diff digest). Approval of one commit does not approve a later commit or a conflict-resolved
149
+ merge. Dirty work is not represented as clean `HEAD` evidence. Record source commit and
150
+ publication commit separately to avoid a self-referential commit hash inside its own record.
151
+
152
+ Proposed lifecycle:
153
+
154
+ ```text
155
+ running → execution-terminal → review/gate evaluated → finalize-prepared
156
+ → immutable records durable → integrated/accepted → cleanup-eligible
157
+ ```
158
+
159
+ `failed`, `blocked` and `rejected` are valid terminal records; persisting them is not a
160
+ successful task-completion claim. A record on an unmerged branch is provisional evidence,
161
+ not canonical accepted project state. Consolidation uses accepted records from the selected
162
+ integration revision plus an explicit control snapshot, not arbitrary branch-local files.
163
+
164
+ Proposed `finalize` must be bounded, idempotent and crash-recoverable: validate owner/revision,
165
+ freeze input digests, persist records, verify them by reading back, then update the control
166
+ receipt. A missing/different record or changed source revision prevents cleanup. Do not mark
167
+ finalized before the durable writes succeed. Recovery uses the journal/receipt to complete
168
+ or reject a partial attempt without recreating a different result under the same ID.
169
+
170
+ **A file written in a worktree is not yet safe against worktree removal.** Cleanup eligibility
171
+ requires records/evidence referenced by an accepted retained Git revision (or an explicitly
172
+ configured durable store); merely staged/untracked records are insufficient. Local ignored
173
+ logs must be promoted or have their missing evidence represented before that receipt.
174
+
175
+ Leerness can enforce finalize on its own adapter cleanup path, not intercept arbitrary
176
+ external `git worktree remove --force`, manual deletion or `git prune`. There is no promise
177
+ of an unbypassable Git removal hook. Worktree locks may reduce accidental pruning but are
178
+ not a substitute for finalization and user-authorized cleanup. Never delete a worktree or
179
+ its metadata as a side effect of inspect, handoff or migration.
180
+
181
+ ## 6. Staged delivery and migration order
182
+
183
+ | Stage | Tracking | Deliverable / gate |
184
+ |---|---|---|
185
+ | Audit and design | T-0173 | Actual state map, reuse inventory, structural risks, compatibility order and preview; no runtime activation. |
186
+ | Scope foundation | M-0014 / T-0174 / P-0020 | Resolver and `state inspect` read-only CLI contract; current/proposed locations distinguished; no stores moved and no new execution adapter. |
187
+ | Private runtime | M-0015 / T-0175 | Compatibility-only guard first (P-0021 proposed / T-0180), then separately approved migration of coupled runtime units; ownership and interrupted recovery. See [source-backed migration design](worktree-runtime-migration.md). |
188
+ | Common control | M-0016 / T-0176 | Task identity, claims, owner generations, version/revision compatibility and bounded ControlStore transactions. |
189
+ | Durable records | M-0017 / T-0177 | Append-only memory import, run/review records and finalize durability receipts; old evidence retained. |
190
+ | Views and adapters | M-0018 / T-0178 | Accepted-input snapshot, single-writer summaries, integrity/claims/CLI/MCP/cleanup integration; legacy window closed only with evidence. |
191
+
192
+ M-0010's existing v2 validators stay usable and legacy runtime writes stay authoritative.
193
+ Finalize its storage/migration design against M-0014 before implementing another independent
194
+ path migration. Runtime activation must also satisfy M-0015 compatibility and M-0016 control
195
+ contracts. M-0011 dispatch, M-0012 live visualization and M-0013 UI consume the same State API.
196
+ Do not prematurely move the already documented role files into `config/`: filename aliases,
197
+ CLI/MCP compatibility and the migration manifest must be specified first.
198
+
199
+ Migration rules:
200
+
201
+ - Inventory all writers/readers and classify fields, not just filenames. Snapshot original
202
+ bytes, inventory digest, source schema and Git tracking before asking to apply.
203
+ - Legacy memory import must distinguish missing JSON from corrupt/invalid JSON. Existing
204
+ permissive loaders may fall back to stale Markdown; reuse parsers/renderers only behind a
205
+ strict source selection/validation boundary. Invalid canonical data stops migration and
206
+ preserves all original bytes, rather than silently promoting a Markdown fallback.
207
+ - Quiesce all participating writers; a new lock cannot stop an old CLI that does not know it.
208
+ Known legacy clients block activation, and absence of observed clients does not prove
209
+ quiescence. Require an explicit maintenance boundary; unknown participants defer activation.
210
+ Do not claim mixed-version safety from a new manifest alone. Old clients must be
211
+ upgraded/stopped before the boundary moves; a pre-write check alone is not fencing.
212
+ - Stage copied data and validate digests, IDs, counters and provenance before activating one
213
+ versioned manifest. Use single-authority writes; do not create two independent writable
214
+ JSON/Markdown or legacy/v2 truths during a compatibility window.
215
+ - Preserve originals as explicitly labelled legacy archives. Never blanket-untrack or delete
216
+ `.leerness`. Index/ignore changes and project instructions are part of an approved migration.
217
+ - Pre-activation rollback preserves bytes. Post-activation rollback must first reconcile
218
+ newer events/records; copying an old backup over new writes is not rollback.
219
+ - Default handoff and inspect remain tracked-file-read-only. Explicit project memory,
220
+ consolidation and finalization are the durable-write boundaries.
221
+
222
+ ## 7. Acceptance matrix
223
+
224
+ - Main + two real linked worktrees: private paths differ, common paths match, sibling
225
+ subprojects remain separate; branch switch/detached HEAD do not change runtime identity.
226
+ - Same worktree, two sessions: no current-run/evidence cross-talk; absent/invalid explicit
227
+ session identity fails safely for concurrent mutation instead of silently borrowing ownership.
228
+ - Non-Git, Git missing, bare, broken gitfile, moved worktree, submodule, spaces, Unicode,
229
+ Windows alias/case/junction, permission errors and inherited Git location overrides.
230
+ - Inspect/show: byte and mtime snapshots of project and Git metadata remain identical, no
231
+ cache/usage/salt/lease/lock creation, no subprocess provider calls, bounded stdout/JSON errors.
232
+ - Multi-process claim/lease races, owner expiry/replacement, clock movement, duplicate IDs,
233
+ truncated JSONL, corrupt/future schema, failed writes and interruption at every commit point.
234
+ - Independent records merge without shared counter edits; conflicting IDs/policy revisions
235
+ fail closed; decision disagreement stays explicit; generated views reproduce from digests.
236
+ - Finalize replay, record durability failure, stale review commit, rejected review, failed
237
+ tests, missing provenance, evidence outside retained Git and worktree-removal preflight.
238
+ - Legacy fixtures, handoff/MCP presence, file-lease and role-fallback probes plus installed
239
+ cleanroom and supported Node/Windows/POSIX release tests before enabling new runtime writes.
240
+
241
+ ## Inspection optimization and verification boundaries
242
+
243
+ Each valid inspection makes one Git topology query through the existing gateway. On Windows
244
+ the gateway also runs the trusted System32 executable locator; the normal console encoding
245
+ bootstrap is unchanged. Inspection skips unrelated `npm root -g` skillpack discovery and
246
+ does not call providers, register presence, take usage locks, or write caches.
247
+ Workspace discovery enumerates the two immediate workspace directories once. Inventory covers
248
+ known leaves in both existing locations (selected/unselected are explicit), with cached parent
249
+ metadata: it does not read state contents,
250
+ enumerate runtime descendants, query Git tracking, or certify record/schema integrity.
251
+ No long-lived path cache is used: moving a worktree or changing the target refreshes topology.
252
+
253
+ `npm run test:state-scopes` checks selector compatibility, real main/two linked worktrees,
254
+ project namespaces, moved/detached worktrees, actual submodule topology, aliases, Git failure
255
+ taxonomy, and CLI byte/mtime preservation with normal bookkeeping enabled. The scope probe
256
+ also passed on Node 18/Windows; POSIX execution remains a separate CI check, not implied by it.
257
+ Later concurrency/finalize/adapter acceptance bullets above do not apply to the implemented
258
+ inspection-only stage and are not reported complete by its tests.
259
+
260
+ ## Not included in P-0020
261
+
262
+ No automatic migration, control database, task dispatch, paid provider calls, source lease
263
+ relocation, decision import, finalize command, worktree deletion or dashboard is implied by
264
+ the path-inspection preview. Existing review/release rules still apply to implementation.
265
+ Later stages require their own
266
+ implementation scope and verification; this design does not mark them complete.