@cassiomc1/forgeloop 1.14.0 → 2.0.0-rc.4

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 (250) hide show
  1. package/CONTRIBUTING.md +11 -7
  2. package/DOCS_INDEX.md +2 -1
  3. package/ENG/c-development-eng.md +2 -2
  4. package/ENG/cpp-development-eng.md +2 -2
  5. package/ENG/dotnet-aspnetcore-development-eng.md +7 -1
  6. package/ENG/flutter-development-eng.md +4 -2
  7. package/ENG/go-development-eng.md +2 -2
  8. package/ENG/java-development-eng.md +3 -3
  9. package/ENG/nodejs-backend-development-eng.md +8 -4
  10. package/ENG/php-development-eng.md +3 -3
  11. package/ENG/python-development-eng.md +205 -0
  12. package/ENG/rust-development-eng.md +42 -1
  13. package/ENG/sql-development-eng.md +4 -4
  14. package/ENG/swift-development-eng.md +5 -4
  15. package/ENG/test-code-eng.md +1 -1
  16. package/ENG/typescript-development-eng.md +4 -4
  17. package/GUIDE_ROUTER.md +6 -0
  18. package/LICENSE +133 -21
  19. package/LOOP_SYSTEM_DESIGN.md +10 -10
  20. package/README.md +30 -40
  21. package/THIRD_PARTY_NOTICES.md +3 -4
  22. package/completions/_forgeloop +11 -3
  23. package/completions/forgeloop.bash +19 -3
  24. package/completions/forgeloop.fish +61 -1
  25. package/docs/AGENT_PROTOCOL_SUMMARY.md +50 -40
  26. package/docs/ARTIFACT_REFERENCE.md +50 -37
  27. package/docs/CLI_REFERENCE.md +147 -3
  28. package/docs/DOCUMENTATION_GUIDE.md +6 -4
  29. package/docs/GETTING_STARTED.md +3 -3
  30. package/docs/PACKAGE_CONTENTS.md +14 -13
  31. package/docs/RELEASE_CHECKLIST.md +110 -10
  32. package/docs/SELF_HOSTED_TESTS.md +121 -0
  33. package/docs/TROUBLESHOOTING.md +33 -0
  34. package/docs/documentation-manifest.json +25 -12
  35. package/package.json +15 -10
  36. package/schemas/task-bundle.schema.json +7 -2
  37. package/scripts/generate-agent-protocol-summary.mjs +2 -2
  38. package/scripts/run-mcp-clean-tests.mjs +30 -0
  39. package/src/adapters/agent-browser/provider.js +14 -11
  40. package/src/adapters/emulated-services/provider.js +11 -4
  41. package/src/adapters/temporary-paths.js +18 -0
  42. package/src/cli.js +146 -210
  43. package/src/commands/action-show.js +5 -2
  44. package/src/commands/advance.js +5 -1
  45. package/src/commands/baseline.js +13 -11
  46. package/src/commands/checkpoint-revalidate.js +24 -8
  47. package/src/commands/complete.js +19 -3
  48. package/src/commands/contract-create.js +3 -1
  49. package/src/commands/doctor.js +36 -13
  50. package/src/commands/gate-record.js +20 -31
  51. package/src/commands/gate-revalidate.js +11 -13
  52. package/src/commands/handoff-list.js +6 -1
  53. package/src/commands/handoff-show.js +6 -1
  54. package/src/commands/migrate-protocol.js +2 -2
  55. package/src/commands/model-route.js +9 -1
  56. package/src/commands/next.js +7 -1
  57. package/src/commands/policy-diff.js +25 -4
  58. package/src/commands/progress.js +17 -11
  59. package/src/commands/record-diagnosis.js +3 -0
  60. package/src/commands/semantic-plan.js +26 -2
  61. package/src/commands/status.js +6 -1
  62. package/src/commands/storage-backup.js +15 -0
  63. package/src/commands/storage-migrate.js +5 -0
  64. package/src/commands/storage-migration-resume.js +5 -0
  65. package/src/commands/storage-migration-status.js +44 -0
  66. package/src/commands/storage-restore-resume.js +7 -0
  67. package/src/commands/storage-restore.js +8 -0
  68. package/src/commands/task-abandon.js +1 -1
  69. package/src/commands/task-list.js +8 -43
  70. package/src/commands/task-migrate.js +3 -3
  71. package/src/commands/task-recover.js +10 -2
  72. package/src/commands/task-scope.js +18 -2
  73. package/src/commands/task-show.js +16 -2
  74. package/src/commands/validate-protocol.js +29 -14
  75. package/src/commands/validate-receipt.js +8 -1
  76. package/src/config/guides.json +4 -0
  77. package/src/core/action-ledger-projection.js +10 -3
  78. package/src/core/actions.js +38 -75
  79. package/src/core/activation.js +11 -3
  80. package/src/core/approvals.js +17 -51
  81. package/src/core/artifact-registry.js +59 -2
  82. package/src/core/artifacts.js +81 -27
  83. package/src/core/attachment-paths.js +45 -0
  84. package/src/core/attestation-coverage.js +6 -1
  85. package/src/core/attestation-verifier.js +21 -2
  86. package/src/core/attestation.js +17 -6
  87. package/src/core/audit.js +6 -1
  88. package/src/core/bundles.js +160 -66
  89. package/src/core/checkpoint-revalidation.js +12 -10
  90. package/src/core/cli-command-definitions.js +109 -7
  91. package/src/core/code-manifest.js +9 -3
  92. package/src/core/command-executors.js +182 -126
  93. package/src/core/command-input.js +21 -1
  94. package/src/core/completion-artifacts.js +85 -36
  95. package/src/core/completion-ownership.js +19 -8
  96. package/src/core/completion-recovery-rebind.js +11 -1
  97. package/src/core/completion.js +60 -9
  98. package/src/core/conformance.js +2 -1
  99. package/src/core/continuity-lint.js +13 -2
  100. package/src/core/continuity-reconciliation.js +16 -3
  101. package/src/core/continuity.js +16 -8
  102. package/src/core/contract-bootstrap-recovery.js +42 -41
  103. package/src/core/contract-revision.js +8 -9
  104. package/src/core/decision/artifact.js +2 -1
  105. package/src/core/decision/events.js +9 -6
  106. package/src/core/decision/resolver.js +19 -11
  107. package/src/core/decision/service.js +63 -38
  108. package/src/core/decision/task-bindings.js +6 -1
  109. package/src/core/diagnosis-model.js +11 -0
  110. package/src/core/diagnosis.js +10 -1
  111. package/src/core/diagnostic-projection.js +29 -12
  112. package/src/core/diagnostic-record.js +28 -3
  113. package/src/core/efficiency.js +6 -1
  114. package/src/core/events.js +316 -97
  115. package/src/core/execution-prerequisites.js +22 -13
  116. package/src/core/execution.js +26 -40
  117. package/src/core/filesystem.js +11 -0
  118. package/src/core/gate-artifact.js +105 -19
  119. package/src/core/gate-provenance.js +6 -5
  120. package/src/core/handoff.js +26 -14
  121. package/src/core/history.js +39 -35
  122. package/src/core/hypothesis-projection.js +32 -0
  123. package/src/core/information-gain-projection.js +104 -100
  124. package/src/core/inspect.js +12 -3
  125. package/src/core/integration-invocation-policy.js +10 -0
  126. package/src/core/integration-resources.js +41 -34
  127. package/src/core/ledger-event-collection.js +189 -0
  128. package/src/core/ledger-relations.js +23 -0
  129. package/src/core/native-storage.js +12 -0
  130. package/src/core/next-action-continuity.js +31 -9
  131. package/src/core/next-action-phases.js +1 -1
  132. package/src/core/next-action-review-phase.js +2 -2
  133. package/src/core/next-action-verification-phase.js +2 -1
  134. package/src/core/next-action.js +21 -2
  135. package/src/core/phase.js +138 -93
  136. package/src/core/policy-engine.js +12 -13
  137. package/src/core/preflight-consistency.js +72 -59
  138. package/src/core/preflight-loaders.js +19 -12
  139. package/src/core/preflight-model.js +6 -1
  140. package/src/core/preflight.js +29 -12
  141. package/src/core/progress.js +21 -10
  142. package/src/core/protocol-info.js +3 -0
  143. package/src/core/protocol-migration.js +7 -6
  144. package/src/core/reconcile-closure.js +162 -24
  145. package/src/core/recovery-history.js +66 -47
  146. package/src/core/reflection.js +72 -77
  147. package/src/core/responsibility.js +25 -13
  148. package/src/core/route-artifact.js +13 -3
  149. package/src/core/runtime-context.js +5 -0
  150. package/src/core/schema-validation.js +8 -3
  151. package/src/core/settlement.js +12 -1
  152. package/src/core/signing/bundle-file.js +53 -0
  153. package/src/core/signing/bundle-reference.js +15 -0
  154. package/src/core/signing/sigstore.js +60 -33
  155. package/src/core/signing/statement-file.js +27 -0
  156. package/src/core/storage-runtime-registry.js +41 -0
  157. package/src/core/structural-quality/artifacts.js +20 -14
  158. package/src/core/structural-quality/service.js +81 -56
  159. package/src/core/target-layout.js +1 -1
  160. package/src/core/task-claim-state.js +52 -18
  161. package/src/core/task-command.js +21 -1
  162. package/src/core/task-conflict-inspection.js +112 -107
  163. package/src/core/task-descriptor.js +29 -22
  164. package/src/core/task-discovery.js +288 -90
  165. package/src/core/task-lock.js +66 -306
  166. package/src/core/task-migration-validation.js +5 -5
  167. package/src/core/task-migration.js +17 -287
  168. package/src/core/task-snapshot.js +51 -8
  169. package/src/core/task-storage-migration.js +18 -0
  170. package/src/core/templates.js +20 -8
  171. package/src/core/test-intelligence/prune.js +5 -1
  172. package/src/core/trace.js +154 -99
  173. package/src/core/trajectory-evaluation.js +15 -3
  174. package/src/core/trajectory-metrics.js +47 -32
  175. package/src/core/transaction.js +16 -288
  176. package/src/core/verification-scope.js +20 -12
  177. package/src/core/work-state.js +100 -59
  178. package/src/repository-index/binary-manager.js +15 -7
  179. package/src/storage/artifact-bytes.js +22 -0
  180. package/src/storage/attachment-binding.js +11 -0
  181. package/src/storage/attachment-files.js +82 -0
  182. package/src/storage/attachment-references.js +36 -0
  183. package/src/storage/backup.js +53 -0
  184. package/src/storage/capabilities.js +31 -0
  185. package/src/storage/connection-owner.js +119 -0
  186. package/src/storage/connection.js +376 -0
  187. package/src/storage/existing-project-scope.js +45 -0
  188. package/src/storage/export-destination.js +44 -0
  189. package/src/storage/exporter.js +284 -0
  190. package/src/storage/file-durability.js +46 -0
  191. package/src/storage/importer.js +461 -0
  192. package/src/storage/index.js +74 -0
  193. package/src/storage/leases.js +63 -0
  194. package/src/storage/ledger-event-snapshot.js +150 -0
  195. package/src/storage/ledger-relations.js +220 -0
  196. package/src/storage/legacy-maintenance-boundary.js +18 -0
  197. package/src/storage/legacy-signature.js +115 -0
  198. package/src/storage/maintenance-handoff.js +129 -0
  199. package/src/storage/maintenance-owner.js +42 -0
  200. package/src/storage/maintenance.js +310 -0
  201. package/src/storage/metadata-json.js +35 -0
  202. package/src/storage/migration-activate.js +59 -0
  203. package/src/storage/migration-archive.js +57 -0
  204. package/src/storage/migration-attachment-root.js +61 -0
  205. package/src/storage/migration-candidate.js +398 -0
  206. package/src/storage/migration-publication.js +143 -0
  207. package/src/storage/migration-rollback-admission.js +54 -0
  208. package/src/storage/migration-rollback-preparation.js +132 -0
  209. package/src/storage/migration-rollback-publication.js +183 -0
  210. package/src/storage/migration-rollback-release.js +66 -0
  211. package/src/storage/migration-rollback-source-stage.js +103 -0
  212. package/src/storage/migration-rollback.js +43 -0
  213. package/src/storage/migration-source-partition.js +56 -0
  214. package/src/storage/migration-source.js +255 -0
  215. package/src/storage/migration-terminal.js +39 -0
  216. package/src/storage/migration-validation.js +75 -0
  217. package/src/storage/migration.js +43 -0
  218. package/src/storage/operational-attachments.js +49 -0
  219. package/src/storage/operational-context.js +50 -0
  220. package/src/storage/project-backup.js +98 -0
  221. package/src/storage/project-bootstrap.js +24 -0
  222. package/src/storage/project-boundary.js +79 -0
  223. package/src/storage/project-read-snapshot.js +18 -0
  224. package/src/storage/project-replacement.js +137 -0
  225. package/src/storage/project-restore.js +203 -0
  226. package/src/storage/record-definitions.js +45 -0
  227. package/src/storage/repository.js +507 -0
  228. package/src/storage/restore-execution-provenance.js +26 -0
  229. package/src/storage/restore-journal.js +62 -0
  230. package/src/storage/restore-layout.js +30 -0
  231. package/src/storage/restore-preparation.js +84 -0
  232. package/src/storage/restore-replacement-archive.js +162 -0
  233. package/src/storage/restore-replacement-owner.js +31 -0
  234. package/src/storage/restore-replacement-preparation.js +128 -0
  235. package/src/storage/restore-replacement.js +142 -0
  236. package/src/storage/restore-terminal.js +32 -0
  237. package/src/storage/runtime.js +31 -0
  238. package/src/storage/schema.js +214 -0
  239. package/src/storage/singleton-import.js +84 -0
  240. package/src/storage/snapshot-continuity.js +16 -0
  241. package/src/storage/snapshot.js +36 -0
  242. package/src/storage/storage-marker.js +79 -0
  243. package/src/storage/task-guards.js +107 -0
  244. package/src/storage/topology.js +42 -0
  245. package/src/storage/transaction.js +179 -0
  246. package/src/storage/unit-of-work.js +667 -0
  247. package/src/storage/windows-process-incarnation.js +206 -0
  248. package/.forgeloop/forgeloop.gitignore +0 -4
  249. package/LICENSE-DOCS.md +0 -13
  250. package/src/core/transaction-maintenance.js +0 -70
@@ -1,12 +1,17 @@
1
1
  import { readdir } from "node:fs/promises";
2
+ import { setImmediate as nextTurn } from "node:timers/promises";
2
3
  import { ensureWithin, fileExists } from "./filesystem.js";
3
4
  import { getPackageRoot } from "./templates.js";
4
5
  import { TASK_STATE_ROOT, TASK_ARTIFACT_FILES, taskArtifactPath } from "./task-paths.js";
5
6
  import { readTaskDescriptor } from "./task-descriptor.js";
6
- import { readJsonArtifact } from "./artifacts.js";
7
+ import { readJsonArtifact, readPortableJsonArtifact } from "./artifacts.js";
7
8
  import { readLockInfo } from "./task-lock.js";
8
9
  import { taskStorageKey } from "./task-identity.js";
10
+ import { withEventLedgerAudit } from "./events.js";
9
11
  import { resolveTaskClaimState } from "./task-claim-state.js";
12
+ import { getOperationalStore, operationalArtifactExists } from "../storage/operational-context.js";
13
+ import { withNativeReadScope } from "./native-storage.js";
14
+ import { withProjectReadSnapshot } from "../storage/project-read-snapshot.js";
10
15
 
11
16
  /**
12
17
  * Explicitly recognized legacy-incidental artifacts that may legitimately
@@ -55,124 +60,313 @@ async function classifyDescriptorlessTaskDirectory(target, taskKey) {
55
60
  };
56
61
  }
57
62
 
58
- export async function discoverTasks(target, packageRoot = getPackageRoot()) {
63
+ async function readDiscoveryState(target, taskId, descriptor, packageRoot) {
64
+ // Check work-state if available
65
+ let state = null;
66
+ let phase = null;
67
+ let lastUpdated = descriptor.updatedAt ?? descriptor.createdAt;
68
+ try {
69
+ const reader = getOperationalStore(target) ? readJsonArtifact : readPortableJsonArtifact;
70
+ const stateArtifact = await reader(
71
+ target,
72
+ taskArtifactPath(taskId, "state"),
73
+ "work-state",
74
+ packageRoot,
75
+ );
76
+ state = stateArtifact.value;
77
+ phase = state.phase ?? null;
78
+ if (state.lastUpdated) {
79
+ lastUpdated = state.lastUpdated;
80
+ }
81
+ } catch {
82
+ // State might not exist yet
83
+ }
84
+ return { state, phase, lastUpdated };
85
+ }
86
+
87
+ async function discoveryArtifactExists(target, taskId, kind, filename) {
88
+ return operationalArtifactExists(target, taskArtifactPath(taskId, kind)) ?? await fileExists(filename);
89
+ }
90
+
91
+ export async function discoverTasks(target, packageRoot = getPackageRoot(), { taskKeys = null } = {}) {
92
+ return discoverTasksInScope(target, packageRoot, taskKeys, DISCOVERY_PROJECTIONS.FULL);
93
+ }
94
+
95
+ /**
96
+ * Internal integration projection. Discovery still validates every task and
97
+ * records every snapshot observation, but releases the full task graph after
98
+ * retaining only the public project/tasks fields and its canonical sort key.
99
+ */
100
+ export async function discoverTaskSummaries(target, packageRoot = getPackageRoot(), { taskKeys = null } = {}) {
101
+ return discoverTasksInScope(target, packageRoot, taskKeys, DISCOVERY_PROJECTIONS.SUMMARY);
102
+ }
103
+
104
+ /**
105
+ * Internal task-list projection. It runs the same per-task validation and
106
+ * snapshot/CAS observation as full discovery while retaining only the fields
107
+ * consumed by the task-list command.
108
+ */
109
+ export async function discoverTaskListEntries(target, packageRoot = getPackageRoot(), { taskKeys = null } = {}) {
110
+ return discoverTasksInScope(target, packageRoot, taskKeys, DISCOVERY_PROJECTIONS.TASK_LIST);
111
+ }
112
+
113
+ /** Retain a finite page only after every task has passed normal discovery. */
114
+ export async function discoverTaskListPage(target, packageRoot = getPackageRoot(), { phase, active, offset, limit }) {
115
+ const retained = [];
116
+ let total = 0;
117
+ const capacity = limit === null ? Infinity : limit === 0 ? 0 : offset + limit;
118
+ const collect = (task, fallbackSortKey) => {
119
+ if ((phase && task.phase !== phase) || (active && (task.claimState !== "ACTIVE" || task.mutationAllowed !== true))) return;
120
+ total += 1;
121
+ if (capacity === 0) return;
122
+ const entry = { sortKey: task.taskId ?? task.taskKey ?? fallbackSortKey, value: task };
123
+ if (capacity === Infinity) { retained.push(entry); return; }
124
+ let low = 0, high = retained.length;
125
+ while (low < high) {
126
+ const middle = Math.floor((low + high) / 2);
127
+ if (retained[middle].sortKey.localeCompare(entry.sortKey) <= 0) low = middle + 1;
128
+ else high = middle;
129
+ }
130
+ if (low >= capacity) return;
131
+ retained.splice(low, 0, entry);
132
+ if (retained.length > capacity) retained.pop();
133
+ };
134
+ await withProjectReadSnapshot(target,
135
+ () => discoverSelectedTasks(target, packageRoot, null, DISCOVERY_PROJECTIONS.TASK_LIST, collect));
136
+ if (capacity === Infinity) retained.sort((a, b) => a.sortKey.localeCompare(b.sortKey));
137
+ return { tasks: retained.slice(offset).map(entry => entry.value), total };
138
+ }
139
+
140
+ const DISCOVERY_PROJECTIONS = Object.freeze({
141
+ FULL: "full",
142
+ SUMMARY: "summary",
143
+ TASK_LIST: "task-list",
144
+ });
145
+
146
+ async function discoverTasksInScope(target, packageRoot, taskKeys, projection) {
147
+ return withProjectReadSnapshot(target,
148
+ () => discoverSelectedTasks(target, packageRoot, taskKeys, projection));
149
+ }
150
+
151
+ function projectTaskList(task) {
152
+ if (task.healthy === false) {
153
+ return {
154
+ taskId: task.taskId ?? null,
155
+ taskKey: task.taskKey,
156
+ directory: task.directory,
157
+ healthy: false,
158
+ error: task.error,
159
+ };
160
+ }
161
+ return {
162
+ taskId: task.taskId,
163
+ taskKey: task.taskKey,
164
+ directory: task.directory,
165
+ healthy: true,
166
+ phase: task.phase,
167
+ writeClaims: task.writeClaims ?? [],
168
+ historicalWriteClaims: task.historicalWriteClaims ?? [],
169
+ effectiveWriteClaims: task.effectiveWriteClaims ?? [],
170
+ claimState: task.claimState,
171
+ recovery: task.recovery,
172
+ mutationAllowed: task.mutationAllowed,
173
+ ownershipValid: task.ownershipValid,
174
+ ownershipErrors: task.ownershipErrors ?? task.errors ?? [],
175
+ reasonCodes: task.reasonCodes ?? [],
176
+ locked: task.locked,
177
+ hasContinuity: task.hasContinuity,
178
+ hasReceipt: task.hasReceipt,
179
+ createdAt: task.createdAt,
180
+ updatedAt: task.updatedAt,
181
+ };
182
+ }
183
+
184
+ function projectTask(task, projection) {
185
+ if (projection === DISCOVERY_PROJECTIONS.FULL) return task;
186
+ if (projection === DISCOVERY_PROJECTIONS.SUMMARY) return projectTaskSummary(task);
187
+ return projectTaskList(task);
188
+ }
189
+
190
+ async function discoverTaskEntry(target, packageRoot, entry, projection) {
191
+ const descriptorArtifact = await readTaskDescriptor(target, entry.name, packageRoot);
192
+ const descriptor = descriptorArtifact.value;
193
+ const taskId = descriptor.taskId;
194
+
195
+ // P1-1: Verify descriptor taskKey matches the actual directory name
196
+ if (descriptor.taskKey !== entry.name) {
197
+ return projectTask({
198
+ taskId: descriptor.taskId ?? null,
199
+ taskKey: entry.name,
200
+ directory: `${TASK_STATE_ROOT}/${entry.name}`,
201
+ healthy: false,
202
+ error: {
203
+ code: "E_TASK_KEY_MISMATCH",
204
+ message: `Task directory key "${entry.name}" does not match descriptor taskKey "${descriptor.taskKey}"`,
205
+ },
206
+ }, projection);
207
+ }
208
+
209
+ const { state, phase, lastUpdated } = await readDiscoveryState(target, taskId, descriptor, packageRoot);
210
+
211
+ // Check lock status
212
+ const lockInfo = await readLockInfo(target, taskId);
213
+
214
+ // Check continuity & receipt presence
215
+ const continuityPath = ensureWithin(target, taskArtifactPath(taskId, "continuity"));
216
+ const hasContinuity = await discoveryArtifactExists(target, taskId, "continuity", continuityPath);
217
+
218
+ const receiptPath = ensureWithin(target, taskArtifactPath(taskId, "receipt"));
219
+ const hasReceipt = await discoveryArtifactExists(target, taskId, "receipt", receiptPath);
220
+
221
+ const claimProjection = await resolveTaskClaimState(target, {
222
+ taskId,
223
+ packageRoot,
224
+ descriptor,
225
+ state,
226
+ });
227
+ const recovery = claimProjection.recovery;
228
+ const directory = `${TASK_STATE_ROOT}/${descriptor.taskKey}`;
229
+ const locked = lockInfo !== null;
230
+
231
+ if (projection === DISCOVERY_PROJECTIONS.SUMMARY) {
232
+ return {
233
+ taskId,
234
+ healthy: true,
235
+ phase,
236
+ mutationAllowed: claimProjection.mutationAllowed !== false,
237
+ };
238
+ }
239
+
240
+ if (projection === DISCOVERY_PROJECTIONS.TASK_LIST) {
241
+ return projectTaskList({
242
+ taskId,
243
+ taskKey: descriptor.taskKey,
244
+ directory,
245
+ healthy: true,
246
+ phase,
247
+ writeClaims: claimProjection.writeClaims,
248
+ historicalWriteClaims: claimProjection.historicalWriteClaims,
249
+ effectiveWriteClaims: claimProjection.effectiveWriteClaims,
250
+ claimState: claimProjection.claimState,
251
+ recovery,
252
+ mutationAllowed: claimProjection.mutationAllowed,
253
+ ownershipValid: claimProjection.valid,
254
+ ownershipErrors: claimProjection.ownershipErrors,
255
+ reasonCodes: claimProjection.reasonCodes,
256
+ locked,
257
+ hasContinuity,
258
+ hasReceipt,
259
+ createdAt: descriptor.createdAt,
260
+ updatedAt: descriptor.updatedAt,
261
+ });
262
+ }
263
+
264
+ return {
265
+ taskId,
266
+ taskKey: descriptor.taskKey,
267
+ healthy: true,
268
+ phase,
269
+ locked,
270
+ lockInfo,
271
+ ...claimProjection,
272
+ ownershipValid: claimProjection.valid,
273
+ recovery,
274
+ operatorRecoveredAt: recovery?.recoveredAt ?? null,
275
+ createdAt: descriptor.createdAt,
276
+ updatedAt: descriptor.updatedAt,
277
+ lastUpdated,
278
+ hasContinuity,
279
+ hasReceipt,
280
+ ...(claimProjection.errors.length > 0 ? { errors: claimProjection.errors } : {}),
281
+ descriptor,
282
+ directory,
283
+ };
284
+ }
285
+
286
+ function projectTaskSummary(task) {
287
+ return {
288
+ taskId: task.taskId,
289
+ healthy: task.healthy !== false,
290
+ phase: task.phase ?? null,
291
+ mutationAllowed: task.mutationAllowed !== false,
292
+ };
293
+ }
294
+
295
+ function compareDiscoveredTasks(a, b) {
296
+ return (a.taskId ?? a.taskKey).localeCompare(b.taskId ?? b.taskKey);
297
+ }
298
+
299
+ function appendDiscoveredTask(tasks, task, projection, fallbackSortKey = null, collect = null) {
300
+ if (collect) { collect(task, fallbackSortKey); return; }
301
+ if (projection === DISCOVERY_PROJECTIONS.FULL) {
302
+ tasks.push(task);
303
+ return;
304
+ }
305
+ tasks.push({ sortKey: task.taskId ?? task.taskKey ?? fallbackSortKey, value: task });
306
+ }
307
+
308
+ function finishDiscoveredTasks(tasks, projection) {
309
+ if (projection !== DISCOVERY_PROJECTIONS.FULL) {
310
+ return tasks.sort((a, b) => a.sortKey.localeCompare(b.sortKey)).map(({ value }) => value);
311
+ }
312
+ return tasks.sort(compareDiscoveredTasks);
313
+ }
314
+
315
+ async function discoverSelectedTasks(target, packageRoot, taskKeys, projection = DISCOVERY_PROJECTIONS.FULL, collect) {
316
+ const store = getOperationalStore(target);
59
317
  const rootPath = ensureWithin(target, TASK_STATE_ROOT);
60
- if (!(await fileExists(rootPath))) {
318
+ if (!store && !(await fileExists(rootPath))) {
61
319
  return [];
62
320
  }
63
321
 
64
322
  let entries = [];
65
323
  try {
66
- entries = await readdir(rootPath, { withFileTypes: true });
324
+ entries = store
325
+ ? (taskKeys ?? store.listTaskKeys()).map(name => ({ name, isDirectory: () => true, isSymbolicLink: () => false }))
326
+ : await readdir(rootPath, { withFileTypes: true });
67
327
  } catch {
68
328
  return [];
69
329
  }
70
330
 
71
331
  const tasks = [];
332
+ let scanned = 0;
72
333
  for (const entry of entries) {
334
+ // Native reads resolve synchronously. Let other requests run between
335
+ // bounded groups without releasing the owned immutable snapshot.
336
+ if (store && !store.transaction && !store.db.isTransaction && scanned > 0 && scanned % 16 === 0) await nextTurn();
337
+ scanned += 1;
73
338
  if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
74
339
  if (!/^[a-f0-9]{64}$/.test(entry.name)) continue;
75
340
 
76
341
  try {
77
- const descriptorArtifact = await readTaskDescriptor(target, entry.name, packageRoot);
78
- const descriptor = descriptorArtifact.value;
79
- const taskId = descriptor.taskId;
80
-
81
- // P1-1: Verify descriptor taskKey matches the actual directory name
82
- if (descriptor.taskKey !== entry.name) {
83
- tasks.push({
84
- taskId: descriptor.taskId ?? null,
85
- taskKey: entry.name,
86
- directory: `${TASK_STATE_ROOT}/${entry.name}`,
87
- healthy: false,
88
- error: {
89
- code: "E_TASK_KEY_MISMATCH",
90
- message: `Task directory key "${entry.name}" does not match descriptor taskKey "${descriptor.taskKey}"`,
91
- },
92
- });
93
- continue;
94
- }
95
-
96
- // Check work-state if available
97
- let state = null;
98
- let phase = null;
99
- let lastUpdated = descriptor.updatedAt ?? descriptor.createdAt;
100
- try {
101
- const stateArtifact = await readJsonArtifact(
102
- target,
103
- taskArtifactPath(taskId, "state"),
104
- "work-state",
105
- packageRoot,
106
- );
107
- state = stateArtifact.value;
108
- phase = state.phase ?? null;
109
- if (state.lastUpdated) {
110
- lastUpdated = state.lastUpdated;
111
- }
112
- } catch {
113
- // State might not exist yet
114
- }
115
-
116
- // Check lock status
117
- const lockInfo = await readLockInfo(target, taskId);
118
-
119
- // Check continuity & receipt presence
120
- const continuityPath = ensureWithin(target, taskArtifactPath(taskId, "continuity"));
121
- const hasContinuity = await fileExists(continuityPath);
122
-
123
- const receiptPath = ensureWithin(target, taskArtifactPath(taskId, "receipt"));
124
- const hasReceipt = await fileExists(receiptPath);
125
-
126
- const claimProjection = await resolveTaskClaimState(target, {
127
- taskId,
128
- packageRoot,
129
- descriptor,
130
- state,
131
- });
132
- const recovery = claimProjection.recovery;
133
-
134
- tasks.push({
135
- taskId,
136
- taskKey: descriptor.taskKey,
137
- healthy: true,
138
- phase,
139
- locked: lockInfo !== null,
140
- lockInfo,
141
- ...claimProjection,
142
- ownershipValid: claimProjection.valid,
143
- recovery,
144
- operatorRecoveredAt: recovery?.recoveredAt ?? null,
145
- createdAt: descriptor.createdAt,
146
- updatedAt: descriptor.updatedAt,
147
- lastUpdated,
148
- hasContinuity,
149
- hasReceipt,
150
- ...(claimProjection.errors.length > 0 ? { errors: claimProjection.errors } : {}),
151
- descriptor,
152
- directory: `${TASK_STATE_ROOT}/${descriptor.taskKey}`,
153
- });
342
+ const readEntry = () => discoverTaskEntry(target, packageRoot, entry, projection);
343
+ const taskId = store?.taskId({ taskKey: entry.name });
344
+ const task = taskId
345
+ ? await withEventLedgerAudit(target, packageRoot, { taskId }, readEntry)
346
+ : await readEntry();
347
+ appendDiscoveredTask(tasks, task, projection, entry.name, collect);
154
348
  } catch (err) {
155
349
  // A directory without a task.json descriptor is not automatically a
156
350
  // task namespace: classify by contents so explicitly recognized legacy
157
351
  // spillover (policy-snapshot.json) stays compatible while any modern
158
352
  // task artifact without a descriptor fails closed instead of silently
159
353
  // reopening the legacy singleton fallback.
160
- if (err.code === "E_TASK_NOT_FOUND" || err.code === "ARTIFACT_MISSING") {
354
+ if (!store && (err.code === "E_TASK_NOT_FOUND" || err.code === "ARTIFACT_MISSING")) {
161
355
  const classification = await classifyDescriptorlessTaskDirectory(target, entry.name);
162
356
  if (classification.kind === "LEGACY_INCIDENTAL") {
163
357
  continue;
164
358
  }
165
- tasks.push({
359
+ appendDiscoveredTask(tasks, projectTask({
166
360
  taskId: null,
167
361
  taskKey: entry.name,
168
362
  directory: `${TASK_STATE_ROOT}/${entry.name}`,
169
363
  healthy: false,
170
364
  error: classification.error,
171
- });
365
+ }, projection), projection, entry.name, collect);
172
366
  continue;
173
367
  }
174
368
  // P1-2: Surface corrupt task namespaces instead of silently hiding them
175
- tasks.push({
369
+ appendDiscoveredTask(tasks, projectTask({
176
370
  taskId: null,
177
371
  taskKey: entry.name,
178
372
  directory: `${TASK_STATE_ROOT}/${entry.name}`,
@@ -181,15 +375,19 @@ export async function discoverTasks(target, packageRoot = getPackageRoot()) {
181
375
  code: err.code ?? "E_TASK_DESCRIPTOR_INVALID",
182
376
  message: err.message ?? String(err),
183
377
  },
184
- });
378
+ }, projection), projection, entry.name, collect);
185
379
  }
186
380
  }
187
381
 
188
- return tasks.sort((a, b) => (a.taskId ?? a.taskKey).localeCompare(b.taskId ?? b.taskKey));
382
+ return finishDiscoveredTasks(tasks, projection);
189
383
  }
190
384
 
191
385
  export async function findTaskById(target, taskId, packageRoot = getPackageRoot()) {
192
- const taskKey = taskStorageKey(taskId);
193
- const tasks = await discoverTasks(target, packageRoot);
194
- return tasks.find((t) => t.healthy !== false && (t.taskId === taskId || t.taskKey === taskKey)) ?? null;
386
+ return withNativeReadScope(target, async () => {
387
+ const taskKey = taskStorageKey(taskId);
388
+ const store = getOperationalStore(target);
389
+ if (store && !store.taskRow(taskKey)) return null;
390
+ const tasks = await discoverTasks(target, packageRoot, store ? { taskKeys: [taskKey] } : {});
391
+ return tasks.find((t) => t.healthy !== false && (t.taskId === taskId || t.taskKey === taskKey)) ?? null;
392
+ });
195
393
  }