@zq-silk/yui 0.15.12 → 0.16.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 (236) hide show
  1. package/ARCHITECTURE.md +3 -2
  2. package/ARCHITECTURE.zh-CN.md +3 -2
  3. package/dist/artifacts/artifactCommitLock.js +16 -55
  4. package/dist/artifacts/taskArtifactRepository.js +3 -3
  5. package/dist/cli/agentConfigurationPicker.js +1 -1
  6. package/dist/cli/commandCatalog.js +57 -41
  7. package/dist/cli/completion.js +18 -18
  8. package/dist/cli/completionWizard.js +1 -1
  9. package/dist/cli/dynamicCompletion.js +1 -1
  10. package/dist/cli/interactionPolicy.js +7 -3
  11. package/dist/cli/invocationAuthority.js +64 -0
  12. package/dist/cli/managedDiagnostics.js +1 -1
  13. package/dist/cli/parseRepeatable.js +35 -0
  14. package/dist/cli/updatePorts.js +26 -5
  15. package/dist/cli.js +1280 -1243
  16. package/dist/commands/agentCommands.js +4 -4
  17. package/dist/commands/capabilityCommands.js +1 -1
  18. package/dist/commands/configCommands.js +6 -57
  19. package/dist/commands/configOverview.js +2 -2
  20. package/dist/commands/durableJobCommands.js +12 -6
  21. package/dist/commands/globalRoleCommands.js +53 -29
  22. package/dist/commands/grantCommands.js +0 -3
  23. package/dist/commands/jobCommands.js +1 -4
  24. package/dist/commands/operatorCommands.js +3 -3
  25. package/dist/commands/projectCommands.js +1 -10
  26. package/dist/commands/resourcesCommands.js +10 -40
  27. package/dist/commands/roleConfiguration.js +2 -6
  28. package/dist/commands/taskActivationCommands.js +4 -8
  29. package/dist/commands/taskAgentCapabilities.js +9 -0
  30. package/dist/commands/taskCommandSupport.js +155 -0
  31. package/dist/commands/taskCommandTypes.js +1 -0
  32. package/dist/commands/taskCommands.js +271 -710
  33. package/dist/commands/taskCompletionGate.js +1 -1
  34. package/dist/commands/taskExecutionCommands.js +1 -1
  35. package/dist/commands/taskFactCommands.js +325 -0
  36. package/dist/commands/taskInputCommands.js +12 -42
  37. package/dist/commands/taskIntegrationCommands.js +17 -57
  38. package/dist/commands/taskNextActionCommand.js +7 -6
  39. package/dist/commands/taskPublicationAdoptCommand.js +3 -3
  40. package/dist/commands/taskPublicationCommands.js +1 -1
  41. package/dist/commands/taskPublicationVerifyCommand.js +1 -1
  42. package/dist/commands/taskRemoteDeliveryCommand.js +2 -94
  43. package/dist/commands/taskUpstreamCommands.js +19 -8
  44. package/dist/commands/workflowCommands.js +6 -1
  45. package/dist/completion/completionInstaller.js +26 -26
  46. package/dist/completion/completionState.js +26 -26
  47. package/dist/completion/fileCompletionManager.js +6 -11
  48. package/dist/config/configCatalog.js +0 -2
  49. package/dist/config/timeZone.js +14 -0
  50. package/dist/config/yuiConfig.js +0 -29
  51. package/dist/context/runContextPack.js +2 -3
  52. package/dist/context/taskCatalog.js +3 -5
  53. package/dist/context/taskContext.js +139 -151
  54. package/dist/context/wakeRunReferences.js +11 -0
  55. package/dist/controller/agentCapabilities.js +58 -0
  56. package/dist/controller/agentRuntimeObserver.js +6 -11
  57. package/dist/controller/clientRuntime.js +22 -14
  58. package/dist/controller/controller.js +9 -22
  59. package/dist/controller/fileSchedulerStoreAdapter.js +117 -212
  60. package/dist/controller/jobClient.js +4 -4
  61. package/dist/controller/jobControl.js +60 -25
  62. package/dist/controller/jobSupervisor.js +4 -4
  63. package/dist/controller/providerRetryDelivery.js +7 -7
  64. package/dist/controller/runtime.js +51 -44
  65. package/dist/controller/runtimeEventProcessor.js +0 -5
  66. package/dist/controller/sessionOwnerReconciliation.js +5 -0
  67. package/dist/controller/taskAgentError.js +35 -0
  68. package/dist/coordination/workMailboxQueue.js +2 -24
  69. package/dist/core/controllerClient.js +2 -2
  70. package/dist/core/fileLockOwner.js +74 -0
  71. package/dist/decision/decision.js +17 -0
  72. package/dist/doctor/doctor.js +18 -9
  73. package/dist/event/taskEvent.js +12 -0
  74. package/dist/execution/workItemExecutionProjection.js +2 -2
  75. package/dist/executor/agentCapabilityConfig.js +43 -0
  76. package/dist/executor/agentConfigurationCatalog.js +49 -11
  77. package/dist/executor/agentExecutor.js +5 -8
  78. package/dist/executor/fileRoleLaunchPlanner.js +11 -53
  79. package/dist/executor/taskAgentCapabilities.js +64 -0
  80. package/dist/integration/deliveryObligation.js +1 -69
  81. package/dist/integration/gitIntegrationService.js +60 -84
  82. package/dist/integration/integrationAttempt.js +3 -14
  83. package/dist/integration/integrationSourceApplication.js +4 -12
  84. package/dist/integration/manifestTags.js +2 -2
  85. package/dist/job/durableJob.js +4 -25
  86. package/dist/job/jobAssignmentScope.js +22 -0
  87. package/dist/job/jobOperation.js +16 -0
  88. package/dist/job/jobRunner.js +2 -1
  89. package/dist/job/stepDirectory.js +15 -0
  90. package/dist/kernel/builtinCapabilities.js +15 -11
  91. package/dist/kernel/kernelPorts.js +1 -17
  92. package/dist/lifecycle/exactRunTerminalization.js +1 -9
  93. package/dist/message/inputControlResolution.js +3 -4
  94. package/dist/message/message.js +17 -20
  95. package/dist/message/messageContinuation.js +1 -1
  96. package/dist/milestone/milestone.js +11 -0
  97. package/dist/observability/orchestrationMetrics.js +24 -1
  98. package/dist/observability/runtimeIdentity.js +1 -17
  99. package/dist/output/agentConfigurationPresentation.js +2 -0
  100. package/dist/output/timePresentation.js +1 -14
  101. package/dist/plugins/pluginService.js +12 -4
  102. package/dist/release/releaseWorkflowPorts.js +45 -96
  103. package/dist/release/runtimeRelease.js +9 -15
  104. package/dist/repository/projectMaintenanceLock.js +23 -64
  105. package/dist/repository/taskBaseFreshness.js +1 -1
  106. package/dist/repository/taskWorkspaceCoordinator.js +13 -22
  107. package/dist/repository/taskWorkspacePreparer.js +29 -25
  108. package/dist/repository/workspaceCleanupInspection.js +5 -5
  109. package/dist/resources/autoResourceGc.js +4 -77
  110. package/dist/resources/liveReferences.js +7 -54
  111. package/dist/resources/resourceDiscovery.js +5 -118
  112. package/dist/resources/resourceGc.js +233 -181
  113. package/dist/resources/resourceRegistrar.js +5 -4
  114. package/dist/resources/resourceRegistry.js +3 -37
  115. package/dist/resources/resourceRegistryStore.js +13 -36
  116. package/dist/resources/sqliteResourceRegistry.js +43 -26
  117. package/dist/review/deltaRecheck.js +1 -1
  118. package/dist/review/reviewAcceptance.js +1 -1
  119. package/dist/review/reviewDecision.js +1 -1
  120. package/dist/review/reviewRound.js +8 -7
  121. package/dist/review/taskFinalReviewContractResolution.js +1 -1
  122. package/dist/runtime/acpProtocol.js +4 -51
  123. package/dist/runtime/acpSession.js +10 -75
  124. package/dist/runtime/acpSessionConfiguration.js +1 -8
  125. package/dist/runtime/agentEndpoint.js +3 -2
  126. package/dist/runtime/agentFailureContext.js +43 -0
  127. package/dist/runtime/agentHost.js +15 -15
  128. package/dist/runtime/codexInteractiveHost.js +2 -2
  129. package/dist/runtime/index.js +0 -1
  130. package/dist/runtime/managedCaller.js +21 -1
  131. package/dist/runtime/providerErrors.js +38 -0
  132. package/dist/runtime/runtimeObservation.js +0 -5
  133. package/dist/runtime/runtimeSessionCandidate.js +2 -3
  134. package/dist/runtime/sessionOwnerIdentity.js +1 -1
  135. package/dist/runtime/structuredProviderHost.js +4 -41
  136. package/dist/runtime/tmuxAdapters.js +6 -8
  137. package/dist/scheduler/activeRoleRunDelivery.js +7 -7
  138. package/dist/scheduler/leaderWakeupProcessor.js +19 -6
  139. package/dist/scheduler/operatorEvent.js +12 -20
  140. package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
  141. package/dist/scheduler/ports.js +5 -8
  142. package/dist/scheduler/roleRunLiveness.js +4 -4
  143. package/dist/scheduler/roleRunStall.js +16 -23
  144. package/dist/scheduler/taskExecutionProjection.js +25 -33
  145. package/dist/scheduler/taskObservabilityProjection.js +0 -1
  146. package/dist/scheduler/taskWake.js +5 -10
  147. package/dist/scheduler/wakeReason.js +2 -0
  148. package/dist/scheduler/wakeupQueue.js +2 -4
  149. package/dist/setup/setupCommand.js +1 -1
  150. package/dist/storage/contextRecords.js +106 -0
  151. package/dist/storage/currentTaskStore.js +1 -1
  152. package/dist/storage/homeLayout.js +13 -17
  153. package/dist/storage/migrations/agentFailureContext.js +22 -0
  154. package/dist/storage/migrations/currentInputContract.js +86 -0
  155. package/dist/storage/migrations/currentRuntimeContract.js +228 -0
  156. package/dist/storage/migrations/historicalVerificationPlan.js +35 -0
  157. package/dist/storage/migrations/integrationContinuation.js +5 -4
  158. package/dist/storage/migrations/narrowAgentFailureContext.js +65 -0
  159. package/dist/storage/migrations/notificationOnlyWakes.js +74 -0
  160. package/dist/storage/migrations/verificationPlanV1.js +162 -0
  161. package/dist/storage/migrations/verificationPolicy.js +74 -0
  162. package/dist/storage/migrations/workItemHistory.js +46 -0
  163. package/dist/storage/persistenceWorker.js +12 -4
  164. package/dist/storage/recordValidation.js +87 -0
  165. package/dist/storage/sqliteSchema.js +142 -1
  166. package/dist/storage/sqliteStore.js +156 -162
  167. package/dist/storage/storageSchema.js +2 -14
  168. package/dist/storage/storageVersions.js +1 -1
  169. package/dist/storage/storeRpc.js +15 -7
  170. package/dist/storage/taskStore.js +3 -7
  171. package/dist/storage/upgrade/upgradeOrchestrator.js +25 -37
  172. package/dist/task/completionReadiness.js +5 -24
  173. package/dist/task/draftPlan.js +0 -53
  174. package/dist/task/nextAction.js +7 -13
  175. package/dist/task/remoteDeliveryService.js +89 -0
  176. package/dist/task/taskActivation.js +2 -25
  177. package/dist/task/taskActivationService.js +0 -2
  178. package/dist/task/taskRecordReference.js +0 -1
  179. package/dist/task/taskSubmission.js +3 -14
  180. package/dist/telemetry/sqliteTelemetryBatch.js +29 -0
  181. package/dist/telemetry/sqliteTelemetryStore.js +49 -49
  182. package/dist/telemetry/telemetryWiring.js +4 -3
  183. package/dist/tmux/tmuxManager.js +29 -20
  184. package/dist/verification/gateArtifact.js +15 -24
  185. package/dist/verification/gateArtifactStore.js +14 -7
  186. package/dist/verification/verificationGateService.js +29 -88
  187. package/dist/verification/verificationPlan.js +27 -72
  188. package/dist/web/assets/client/app.js +1 -1
  189. package/dist/web/assets/client/taskSurface.js +4 -4
  190. package/dist/web/webServer.js +4 -6
  191. package/dist/web/webSnapshot.js +3 -83
  192. package/dist/web/webTaskSurface.js +17 -46
  193. package/dist/workItem/workItem.js +1 -12
  194. package/docs/architecture/capabilities-and-resources.md +14 -2
  195. package/docs/architecture/capabilities-and-resources.zh-CN.md +11 -1
  196. package/docs/managed-turn-and-session-runtime.md +83 -10
  197. package/docs/managed-turn-and-session-runtime.zh-CN.md +65 -11
  198. package/docs/plugin-sdk.md +4 -2
  199. package/docs/provider-runtime.md +3 -1
  200. package/docs/provider-runtime.zh-CN.md +4 -2
  201. package/docs/release-workflow.md +190 -7
  202. package/docs/release-workflow.zh-CN.md +139 -5
  203. package/docs/roles-and-configuration.md +29 -0
  204. package/docs/roles-and-configuration.zh-CN.md +21 -0
  205. package/docs/sqlite-control-plane-design.md +16 -3
  206. package/docs/sqlite-control-plane-design.zh-CN.md +13 -2
  207. package/docs/task-dag-semantics.md +3 -3
  208. package/docs/task-dag-semantics.zh-CN.md +2 -2
  209. package/docs/task-delivery.md +91 -5
  210. package/docs/task-delivery.zh-CN.md +72 -4
  211. package/docs/task-discovery.md +10 -11
  212. package/docs/task-discovery.zh-CN.md +8 -9
  213. package/docs/testing/verification-levels.md +145 -9
  214. package/docs/testing/verification-levels.zh-CN.md +103 -8
  215. package/package.json +1 -1
  216. package/skills/yui-leader/SKILL.md +5 -0
  217. package/skills/yui-leader/references/integration.md +9 -5
  218. package/skills/yui-leader/references/planning.md +9 -3
  219. package/skills/yui-operator/SKILL.md +15 -10
  220. package/skills/yui-runtime/SKILL.md +6 -6
  221. package/skills/yui-runtime/references/recovery.md +47 -0
  222. package/dist/agent/agentRegistry.js +0 -10
  223. package/dist/agentRun/runIdentity.js +0 -22
  224. package/dist/commands/deliveryGuardPreflight.js +0 -30
  225. package/dist/commands/taskIntegrationQueueCommands.js +0 -226
  226. package/dist/commands/taskOverviewCommand.js +0 -377
  227. package/dist/completion/completionWizard.js +0 -125
  228. package/dist/context/dispatchContext.js +0 -110
  229. package/dist/context/wakeNotification.js +0 -144
  230. package/dist/integration/integrationCheckEvidenceReuse.js +0 -53
  231. package/dist/integration/integrationQueueEntry.js +0 -191
  232. package/dist/integration/integrationQueueService.js +0 -810
  233. package/dist/release/fakeReleasePorts.js +0 -55
  234. package/dist/runtime/sessionOwnerRegistry.js +0 -137
  235. package/dist/task/deliveryGuard.js +0 -226
  236. /package/dist/{commands/taskActor.js → task/taskAuthority.js} +0 -0
@@ -1,55 +0,0 @@
1
- /**
2
- * The deterministic double for every external system (GitHub, npm, Home,
3
- * Controller). No real network, process, or filesystem side effect: the
4
- * engine's at-most-once and recovery guarantees are proven against this.
5
- */
6
- export function createFakeReleasePorts(scripts) {
7
- const counters = new Map();
8
- for (const stepId of Object.keys(scripts)) {
9
- counters.set(stepId, { execute: 0, query: 0, keys: [] });
10
- }
11
- const tally = (stepId) => {
12
- let calls = counters.get(stepId);
13
- if (calls === undefined) {
14
- calls = { execute: 0, query: 0, keys: [] };
15
- counters.set(stepId, calls);
16
- }
17
- return calls;
18
- };
19
- const next = (queue, stepId, kind, consumed) => {
20
- if (queue === undefined || consumed >= queue.length) {
21
- throw new Error(`Fake release ports: no more scripted ${kind} outcomes for step ${stepId}.`);
22
- }
23
- const entry = queue[consumed];
24
- return typeof entry === "function" ? entry() : entry;
25
- };
26
- return {
27
- async executeStep(input) {
28
- const calls = tally(input.step.id);
29
- const effect = next(scripts[input.step.id]?.execute, input.step.id, "execute", calls.execute);
30
- calls.execute += 1;
31
- calls.keys.push(input.idempotencyKey);
32
- return effect;
33
- },
34
- async queryStepEffect(input) {
35
- const calls = tally(input.step.id);
36
- const query = next(scripts[input.step.id]?.query, input.step.id, "query", calls.query);
37
- calls.query += 1;
38
- return query;
39
- },
40
- calls(stepId) {
41
- const calls = tally(stepId);
42
- return { execute: calls.execute, query: calls.query, keys: Object.freeze([...calls.keys]) };
43
- },
44
- exhausted() {
45
- for (const [stepId, script] of Object.entries(scripts)) {
46
- const calls = counters.get(stepId);
47
- if ((script.execute?.length ?? 0) !== calls.execute)
48
- return false;
49
- if ((script.query?.length ?? 0) !== calls.query)
50
- return false;
51
- }
52
- return true;
53
- }
54
- };
55
- }
@@ -1,137 +0,0 @@
1
- import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
2
- import { join, resolve } from "node:path";
3
- import { createSessionOwnerIdentity, sessionOwnerProcessKey } from "./sessionOwnerIdentity.js";
4
- /**
5
- * Durable, enumerable physical owner records for runtime Sessions.
6
- *
7
- * One JSON file per process identity under `<home>/runtime/session-owners/`. The
8
- * directory is runtime state (like the tmux socket), not aggregate domain
9
- * state, so it needs no schema migration; it survives a Controller restart and
10
- * stays enumerable after the durable Session map or history is cleared, which
11
- * is exactly the gap the audit proved (durable pointers empty, physical
12
- * processes still live).
13
- */
14
- export class FileSessionOwnerRegistry {
15
- #directory;
16
- constructor(home) {
17
- this.#directory = join(resolve(home), "runtime", "session-owners");
18
- }
19
- get directory() {
20
- return this.#directory;
21
- }
22
- /** Atomically records (or replaces) one Session's owner identity. */
23
- record(identity) {
24
- mkdirSync(this.#directory, { recursive: true, mode: 0o700 });
25
- const target = this.#path(sessionOwnerProcessKey(identity));
26
- const temporary = `${target}.tmp-${process.pid}`;
27
- writeFileSync(temporary, JSON.stringify(identity, null, 2) + "\n", { mode: 0o600 });
28
- renameSync(temporary, target);
29
- }
30
- get(processKey) {
31
- if (!isSafeProcessKey(processKey))
32
- return null;
33
- try {
34
- return parseOwnerRecord(readFileSync(this.#path(processKey), "utf8"));
35
- }
36
- catch {
37
- return null;
38
- }
39
- }
40
- list() {
41
- let entries;
42
- try {
43
- entries = readdirSync(this.#directory, { encoding: "utf8" });
44
- }
45
- catch {
46
- return [];
47
- }
48
- const records = [];
49
- for (const entry of entries) {
50
- if (!entry.endsWith(".json"))
51
- continue;
52
- try {
53
- records.push(parseOwnerRecord(readFileSync(join(this.#directory, entry), "utf8")));
54
- }
55
- catch {
56
- // A malformed record must not hide the exact candidates beside it.
57
- }
58
- }
59
- records.sort((left, right) => left.recordedAt.localeCompare(right.recordedAt));
60
- return records;
61
- }
62
- listForOwner(owner) {
63
- return this.list().filter((record) => (record.owner.scope === owner.scope
64
- && (owner.scope === "global"
65
- || record.owner.taskId === owner.taskId)
66
- && record.owner.roleName === owner.roleName));
67
- }
68
- /** Removes a record whose physical resources were proven absent. */
69
- remove(processKey) {
70
- if (!isSafeProcessKey(processKey))
71
- return;
72
- try {
73
- rmSync(this.#path(processKey), { force: true });
74
- }
75
- catch {
76
- // A stale record is harmless: reconciliation re-verifies physical
77
- // identity before acting on it.
78
- }
79
- }
80
- exists() {
81
- return existsSync(this.#directory);
82
- }
83
- #path(processKey) {
84
- if (!isSafeProcessKey(processKey))
85
- throw new Error("Invalid process key.");
86
- return join(this.#directory, `${processKey}.json`);
87
- }
88
- }
89
- function isSafeProcessKey(processKey) {
90
- return /^[1-9][0-9]*-[0-9]+$/u.test(processKey);
91
- }
92
- function parseOwnerRecord(raw) {
93
- const parsed = JSON.parse(raw);
94
- if (typeof parsed !== "object" || parsed === null) {
95
- throw new Error("Session owner record is invalid.");
96
- }
97
- const record = parsed;
98
- const owner = record.owner;
99
- const tmux = record.tmux;
100
- const providerRoot = record.providerRoot;
101
- if (!["launch-env", "pane-pid", "owned-child"].includes(String(providerRoot.attribution))) {
102
- throw new Error("Session owner process attribution is invalid.");
103
- }
104
- return createSessionOwnerIdentity({
105
- owner: {
106
- scope: owner.scope === "global" ? "global" : "task",
107
- ...(owner.taskId === undefined ? {} : { taskId: String(owner.taskId) }),
108
- roleName: String(owner.roleName)
109
- },
110
- agentId: String(record.agentId),
111
- adapterId: String(record.adapterId),
112
- ...(record.nativeSessionId === undefined
113
- ? {}
114
- : { nativeSessionId: String(record.nativeSessionId) }),
115
- tmux: {
116
- serverName: String(tmux.serverName),
117
- socketPath: String(tmux.socketPath),
118
- sessionName: String(tmux.sessionName),
119
- windowName: String(tmux.windowName),
120
- ...(tmux.panePid === undefined ? {} : { panePid: Number(tmux.panePid) })
121
- },
122
- providerRoot: {
123
- pid: Number(providerRoot.pid),
124
- startIdentity: String(providerRoot.startIdentity),
125
- ...(providerRoot.processGroupId === undefined
126
- ? {}
127
- : { processGroupId: Number(providerRoot.processGroupId) }),
128
- ...(providerRoot.processSessionId === undefined
129
- ? {}
130
- : { processSessionId: Number(providerRoot.processSessionId) }),
131
- attribution: providerRoot.attribution === "pane-pid" ? "pane-pid"
132
- : providerRoot.attribution === "owned-child" ? "owned-child" : "launch-env"
133
- },
134
- ...(record.runtimeRoot === undefined ? {} : { runtimeRoot: String(record.runtimeRoot) }),
135
- recordedAt: new Date(String(record.recordedAt))
136
- });
137
- }
@@ -1,226 +0,0 @@
1
- import { isCompletedTaskReviewEvidenceFromRuns } from "../review/reviewAcceptance.js";
2
- const OPEN_WORK_ITEM_STATUSES = new Set(["open"]);
3
- export function detectDeliveryDuplicates(facts, intent) {
4
- switch (intent.kind) {
5
- case "create-work-item":
6
- return detectWorkItemDuplicates(facts, intent.scope);
7
- case "integration-start":
8
- return detectIntegrationDuplicates(facts, intent);
9
- case "review-request":
10
- return detectReviewDuplicates(facts, intent);
11
- case "complete-task":
12
- return detectCompleteDuplicates(facts);
13
- }
14
- }
15
- /**
16
- * Apply the configured mode. `display` never interferes; `warn` reports every
17
- * match as a warning; `enforce` hard-blocks on exact evidence and warns on
18
- * suspected duplicates.
19
- */
20
- export function evaluateDeliveryGuard(duplicates, mode) {
21
- if (mode === "display" || duplicates.length === 0) {
22
- return { blocked: null, warnings: [] };
23
- }
24
- const exact = duplicates.filter((duplicate) => duplicate.severity === "exact");
25
- const suspected = duplicates.filter((duplicate) => duplicate.severity === "suspected");
26
- if (mode === "enforce" && exact.length > 0) {
27
- return { blocked: exact[0], warnings: suspected };
28
- }
29
- return { blocked: null, warnings: [...exact, ...suspected] };
30
- }
31
- export function formatDeliveryDuplicate(duplicate) {
32
- const refs = duplicate.refs.map((ref) => `${ref.kind} ${ref.id}`).join(", ");
33
- const reuse = duplicate.reuseCommand === undefined
34
- ? ""
35
- : ` Existing proof: ${duplicate.reuseCommand}`;
36
- return `${duplicate.severity === "exact" ? "Exact duplicate" : "Suspected duplicate"}: ${duplicate.reason} (${refs}).${reuse}`;
37
- }
38
- function detectWorkItemDuplicates(facts, scope) {
39
- const wanted = normalizeScope(scope);
40
- const duplicates = [];
41
- for (const item of facts.workItems) {
42
- const existing = normalizeScope({
43
- title: item.title,
44
- objective: item.objective,
45
- acceptance: item.acceptance,
46
- writeProjectIds: item.writeProjectIds
47
- });
48
- const sameScope = existing.title === wanted.title
49
- && existing.objective === wanted.objective
50
- && existing.acceptance === wanted.acceptance
51
- && existing.writeProjectIds === wanted.writeProjectIds;
52
- const ref = { kind: "work-item", id: item.id };
53
- if (sameScope && OPEN_WORK_ITEM_STATUSES.has(item.status)) {
54
- duplicates.push({
55
- severity: "exact",
56
- reason: `Work Item ${item.id} is already open with the identical scope`,
57
- refs: [ref],
58
- reuseCommand: `yui task work show ${facts.task.id}/${item.id}`
59
- });
60
- continue;
61
- }
62
- if (sameScope && item.status === "accepted") {
63
- duplicates.push({
64
- severity: "suspected",
65
- reason: `Work Item ${item.id} already delivered the identical scope; re-creating it may be a duplicate successor`,
66
- refs: [ref],
67
- reuseCommand: `yui task work show ${facts.task.id}/${item.id}`
68
- });
69
- continue;
70
- }
71
- if (!sameScope
72
- && OPEN_WORK_ITEM_STATUSES.has(item.status)
73
- && existing.writeProjectIds === wanted.writeProjectIds
74
- && wanted.writeProjectIds.length > 0
75
- && overlap(existing.acceptance, wanted.acceptance)) {
76
- duplicates.push({
77
- severity: "suspected",
78
- reason: `Open Work Item ${item.id} shares the same Project scope and acceptance lines`,
79
- refs: [ref]
80
- });
81
- }
82
- }
83
- return duplicates;
84
- }
85
- function detectIntegrationDuplicates(facts, intent) {
86
- const duplicates = [];
87
- for (const attempt of facts.integrations) {
88
- if (attempt.projectId !== intent.projectId)
89
- continue;
90
- const sameSource = attempt.source.kind === "work-item"
91
- && attempt.source.workItemId === intent.workItemId
92
- && attempt.source.resultCommit === intent.resultCommit;
93
- const ref = { kind: "integration-attempt", id: attempt.id };
94
- if (sameSource && attempt.status === "committed") {
95
- duplicates.push({
96
- severity: "exact",
97
- reason: `Integration ${attempt.id} already committed this exact WorkItem result`,
98
- refs: [ref],
99
- reuseCommand: `yui task integration show ${facts.task.id}/${attempt.id}`
100
- });
101
- continue;
102
- }
103
- if (sameSource
104
- && (attempt.status === "running" || attempt.status === "validating")) {
105
- duplicates.push({
106
- severity: "suspected",
107
- reason: `Integration ${attempt.id} is already ${attempt.status} for this WorkItem result`,
108
- refs: [ref],
109
- reuseCommand: `yui task integration show ${facts.task.id}/${attempt.id}`
110
- });
111
- }
112
- }
113
- return duplicates;
114
- }
115
- function detectReviewDuplicates(facts, intent) {
116
- const wanted = new Set(intent.taskCandidateCommits.map((commit) => commit.toLowerCase()));
117
- const duplicates = [];
118
- for (const round of facts.reviewRounds) {
119
- if ((round.scope ?? "work-item") !== "task")
120
- continue;
121
- if (round.reviewerRoleName !== intent.reviewerRoleName)
122
- continue;
123
- const commits = new Set((round.taskCandidate?.projects ?? []).map((project) => project.commit.toLowerCase()));
124
- const sameCandidate = commits.size === wanted.size
125
- && commits.size > 0
126
- && [...wanted].every((commit) => commits.has(commit));
127
- if (!sameCandidate)
128
- continue;
129
- const ref = { kind: "review-round", id: round.id };
130
- if (isCompletedTaskReviewEvidenceFromRuns(round, facts.reviewOutcomeEvidence?.runs ?? [])) {
131
- duplicates.push({
132
- severity: "exact",
133
- reason: `Task-final Review ${round.id} already attests this exact head`,
134
- refs: [ref]
135
- });
136
- }
137
- else if (round.status === "pending" || round.status === "running") {
138
- duplicates.push({
139
- severity: "suspected",
140
- reason: `Task-final Review ${round.id} is already ${round.status} for this exact head`,
141
- refs: [ref]
142
- });
143
- }
144
- }
145
- return duplicates;
146
- }
147
- function detectCompleteDuplicates(facts) {
148
- if (facts.task.status === "completed" || facts.task.status === "archived") {
149
- return [{
150
- severity: "exact",
151
- reason: `Task ${facts.task.id} is already ${facts.task.status}`,
152
- refs: [{ kind: "task", id: facts.task.id }]
153
- }];
154
- }
155
- return [];
156
- }
157
- function normalizeScope(scope) {
158
- return {
159
- title: scope.title.trim().toLowerCase(),
160
- objective: scope.objective.trim().toLowerCase(),
161
- acceptance: [...scope.acceptance]
162
- .map((line) => line.trim().toLowerCase())
163
- .filter((line) => line.length > 0)
164
- .sort()
165
- .join("\n"),
166
- writeProjectIds: [...scope.writeProjectIds].map((id) => id.trim()).sort().join(",")
167
- };
168
- }
169
- function overlap(left, right) {
170
- const rightLines = new Set(right.split("\n"));
171
- return left.split("\n").some((line) => line.length > 0 && rightLines.has(line));
172
- }
173
- /**
174
- * Default number of consecutive completed Leader turns that must produce no
175
- * durable delivery change before the budget is exhausted.
176
- */
177
- export const DEFAULT_SEMANTIC_BUDGET_RUNS = 3;
178
- /**
179
- * Evaluate the semantic-progress budget from existing records only. The
180
- * budget is exhausted when the last `runs` Leader AgentRuns all completed and no
181
- * WorkItem/ChangeSet/Integration/Review record changed at or after the first
182
- * of those AgentRuns. Active execution (any AgentRun) never exhausts the budget: a
183
- * slow but progressing Worker or Leader is never interrupted.
184
- */
185
- export function evaluateSemanticBudget(facts, runs = DEFAULT_SEMANTIC_BUDGET_RUNS) {
186
- if (facts.activeRuns.length > 0) {
187
- return {
188
- exhausted: false,
189
- reason: "Active execution is in flight; the budget never interrupts a progressing AgentRun.",
190
- evidence: facts.activeRuns.map((run) => run.id)
191
- };
192
- }
193
- const recentLeaderRuns = facts.leaderRuns
194
- .filter((run) => run.status === "completed")
195
- .slice(-runs);
196
- if (recentLeaderRuns.length < runs) {
197
- return {
198
- exhausted: false,
199
- reason: `Fewer than ${runs} consecutive completed Leader runs.`,
200
- evidence: recentLeaderRuns.map((run) => run.id)
201
- };
202
- }
203
- const firstStartedAt = Math.min(...recentLeaderRuns.map((run) => Date.parse(run.createdAt)));
204
- const changed = latestDeliveryChangeAt(facts);
205
- if (changed >= firstStartedAt) {
206
- return {
207
- exhausted: false,
208
- reason: "A delivery record changed during the recent Leader turns.",
209
- evidence: recentLeaderRuns.map((run) => run.id)
210
- };
211
- }
212
- return {
213
- exhausted: true,
214
- reason: `${runs} consecutive Leader runs produced no durable delivery change; record a diagnosis and wait for new facts instead of creating more records.`,
215
- evidence: recentLeaderRuns.map((run) => run.id)
216
- };
217
- }
218
- function latestDeliveryChangeAt(facts) {
219
- const timestamps = [
220
- ...facts.workItems.map((item) => Date.parse(item.updatedAt)),
221
- ...facts.changeSets.map((changeSet) => Date.parse(changeSet.createdAt)),
222
- ...facts.integrations.map((attempt) => Math.max(Date.parse(attempt.updatedAt), Date.parse(attempt.endedAt ?? attempt.updatedAt))),
223
- ...facts.reviewRounds.map((round) => Math.max(Date.parse(round.createdAt), Date.parse(round.endedAt ?? round.createdAt)))
224
- ];
225
- return timestamps.length === 0 ? 0 : Math.max(...timestamps);
226
- }